banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

多線程爬取西刺高匿代理並驗證可用性

前言#

為什麼要用代理? 不管是爆破還是爬蟲,經常會遇到 IP 被封的情況,此時,自建一個可靠的代理池就顯得很有必要了。

代理有哪些分類 以代理的匿名度來分,可以分為透明代理、匿名代理和高度匿名代理。

  • 使用透明代理,對方伺服器可以知道你使用了代理,並且也知道你的真實 IP。
  • 使用匿名代理,對方伺服器可以知道你使用了代理,但不知道你的真實 IP。
  • 使用高匿名代理,對方伺服器不知道你使用了代理,更不知道你的真實 IP。

此篇文章就是爬取國內比較出名的西刺代理(還有一個快代理也比較出名,爬取步驟和這個差不多)的高匿代理並且驗證可用性。

思路#

獲取信息#

首先,我們需要用 requests 庫把網頁爬下來,然後查看我們需要的信息在哪個標籤頁裡。

可以看到每一個代理的所有信息都包含在一個 tr 標籤中,而每一個信息又都是放在一個 td 標籤中的:

image

那麼我們就可以使用 lxml 庫來解析,用 xpath 來尋找標籤,簡單示例代碼如下:

import requests
from lxml import etree

url = 'https://www.xicidaili.com/nn/1'  # 需要爬取的url
req = requests.get(url)  # 用requests請求
content = req.content.decode("utf-8")  # 將內容解碼
tree = etree.HTML(content)  # 解析html文件
tr_nodes = tree.xpath('.//table[@id="ip_list"]/tr')[1:]  # 使用xpath解析所需要的標籤

用 xpath 獲得指定標籤頁的內容可以參考這篇文章:https://www.cnblogs.com/lei0213/p/7506130.html

在獲得每個端口 ip、端口、類型、速度、連接時間等信息之後,我們可以進行篩選:

  • 將 http、https 類型的代理 ip 分類
  • 將速度及連接時間不理想的 ip 過濾掉

驗證有效性#

之後,我們可以將代理 ip 拿去驗證。

驗證方法的話,網上大部分是再將代理 ip 加載請求頭裡面拿去請求某個網站,比如百度。

如果返回狀態碼是 200 的話,則此代理 ip 有效。

但是這樣太過於依賴網路並且效率也不高,看到一篇文章是用每個 ip 用 telnet 去連接一下,如果能連接上,則是有效的。

這部分主要的一個參數就是超時時間,也就是等待時間,建議設置為 10 秒。

用 telnet 驗證代理 ip 有效性的簡單示例如下:

import telnetlib

def verifyProxy(ip, port):
try:
telnetlib.Telnet(ip, port, timeout=5) # 用 telnet 連接一下,能連通說明代理可用
except:
pass
else:
print("此代理已驗證可用:{}".format(proxies))

if **name** == "**main**":
verifyProxy("113.13.177.80", "9999")

用多執行緒優化#

在獲取信息,驗證有效性都可行之後,發現效率太低了,主要耗費時間的地方有這些:

  • 請求多頁耗費時間
  • 驗證代理需要有一個超時等待時間,後面的隊伍都需要等待

那麼我們可以使用 multiprocessing 庫中的 Pool 執行緒池配合 apply_async () 這個方法使用。

這個執行緒池可以配合 apply () 方法使用,但是 apply () 是阻塞的。

首先主進程開始運行,碰到子進程,操作系統切換到子進程,等待子進程運行結束後,再切換到另外一個子進程,直到所有子進程運行完畢。然後再切換到主進程,運行剩餘的部分。這樣跟單進程串行執行沒什麼區別。

apply_async 是異步非阻塞的。

即不用等待當前進程執行完畢,隨時根據系統調度來進行進程切換。首先主進程開始運行,碰到子進程後,主進程仍可以先運行,等到操作系統進行進程切換的時候,再交給子進程運行。可以做到不等待子進程執行完畢,主進程就已經執行完畢,並退出程序。

由於我們是獲取到代理、驗證有效性就行,不要求順序,不需要像爬取小說那樣爬取之後需要按照章節順序存入文件。

所以我們這裡使用 apply_async 是最合適的。

multiprocessing 庫中的 Pool 執行緒池配合 apply_async () 這個方法的簡單示例代碼如下:

from multiprocessing import Pool

def example(proxies):
if type(proxies) == 'http':
with open("1.txt", 'a') as f:
f.write(proxies+"\n")
else:
with open("2.txt", 'a') as f:
f.write(proxies+"\n")

if **name** == "**main**":
proxy = []
with open("tmp.txt", 'r') as f:
proxy = f.read().splitlines()
pool = Pool() # 新建一個執行緒池
for line in proxy:
pool.apply_async(target=example, args=(line,)) # target 這個參數名可以省略,建議在最後一個參數後面加上一個,
pool.close() # 創建進程池之後必須關閉
pool.join() # 加入阻塞隊列,則要上面的代碼執行完畢之後,才會執行後面的代碼

具體代碼實現#

關鍵地方都已做了註解:

#!/usr/bin/python

# -_- coding: utf-8 -_-

'''
@author: soapffz
@function: 多執行緒爬取西刺高匿代理並驗證可用性
@time: 2019-01-21
'''

import requests
from lxml import etree
import re
import telnetlib
import threading
from multiprocessing import Pool
import os
import timeit

path = os.path.join(os.path.expanduser("~")+"\\") # 爬下來未驗證的的代理先放在當前用戶根目錄下
http_tmp_path = os.path.join(path+"http_tmp.txt")
https_tmp_path = os.path.join(path+"https_tmp.txt")

# 驗證過的代理就放在當前用戶桌面

http_proxy_path = os.path.join(path+"Desktop"+"\\"+"http_proxy.txt")
https_proxy_path = os.path.join(path+"Desktop"+"\\"+"https_proxy.txt")
http_proxy = []
https_proxy = []

def get_nn_proxy(page_num):
url = "https://www.xicidaili.com/nn/{}".format(page_num)
headers = {
"User-Agent": 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0'} # 本來還測試使用隨機 User-Agent 頭,後來發現沒用,西刺封的是 ip
req = requests.get(url, headers=headers)
print("正在爬取第{}頁的內容...".format(page_num))
content = req.content.decode("utf-8")
tree = etree.HTML(content) # 用 xpath 獲得總的 ip_list
tr_nodes = tree.xpath('.//table[@id="ip_list"]/tr')[1:]
for tr_node in tr_nodes:
td_nodes = tr_node.xpath('./td') # 用 xpath 獲得單個 ip 的標籤
speed = int(
re.split(r":|%", td_nodes[6].xpath('./div/div/@style')[0])[1]) # 獲得速度的值
conn_time = int(
re.split(r":|%", td_nodes[7].xpath('./div/div/@style')[0])[1]) # 獲得連接時間的值
if(speed <= 95 | conn_time <= 95): # 如果速度和連接時間都不理想,就跳過這個代理
continue
ip = td_nodes[1].text
port = td_nodes[2].text
proxy_type = td_nodes[4].text
ip_type = td_nodes[5].text.lower()
proxy = "{}:{}".format(ip, port)
if ip_type == 'http':
with open(http_tmp_path, 'a') as f:
f.write("http://{}".format(proxy)+"\n") # 獲得爬下來的 http 代理並存在臨時文件中
else:
with open(https_tmp_path, 'a') as f: # 獲得爬下來的 https 代理並存在臨時文件中
f.write("https://{}".format(proxy)+"\n")

def verifyProxy(proxies):
ree = re.split(r'//|:', proxies)
ip_type = ree[0]
ip = ree[2]
port = ree[3]
try:
telnetlib.Telnet(ip, port, timeout=5) # 用 telnet 連接一下,能連通說明代理可用
except:
pass
else:
print("此代理已驗證可用:{}".format(proxies))
if ip_type == 'http':
with open(http_proxy_path, 'a') as f:
f.write(proxies+"\n")
else:
with open(https_proxy_path, 'a') as f:
f.write(proxies+"\n")

def clear_cache(path):
if os.path.exists(path):
os.remove(path)

if **name** == "**main**":
start_time = timeit.default_timer()
clear_cache(http_tmp_path)
clear_cache(https_tmp_path)
clear_cache(http_proxy_path)
clear_cache(https_proxy_path)
pool = Pool()
for i in range(1, 11): # 這裡爬取的是 1 到 10 頁的高匿代理,自行根據需要修改參數
pool.apply_async(get_nn_proxy, args=(i,))
pool.close()
pool.join()
if not os.path.exists(http_tmp_path): # 如果 ip 被 ban 掉,則獲取到的每一頁代理都是空列表,不會生成文件
print("你的 ip 已經被西刺 ban 掉了,請使用 ipconfig /release 之後使用 ipconfig /renew 換 ip 或者掛 vpn!")
os._exit(0)
with open(http_tmp_path, 'r') as f:
http_proxy = f.read().splitlines()
Unhttp_proxy_num = len(http_proxy)
with open(https_tmp_path, 'r') as f:
https_proxy = f.read().splitlines()
Unhttps_proxy_num = len(https_proxy)
pool2 = Pool()
pool3 = Pool()
for proxies in http_proxy:
pool2.apply_async(verifyProxy, args=(proxies,))
for proxies in https_proxy:
pool3.apply_async(verifyProxy, args=(proxies,))
pool2.close()
pool3.close()
pool2.join()
pool3.join()
http_proxy.clear()
https_proxy.clear()
with open(http_proxy_path, 'r') as f:
http_proxy = f.read().splitlines()
http_proxy_num = len(http_proxy)
with open(https_proxy_path, 'r') as f:
https_proxy = f.read().splitlines()
https_proxy_num = len(https_proxy)
print(http_proxy)
print(https_proxy)
print("已存儲在 http_proxy 和 https_proxy 這兩個列表中並已將 txt 保存在桌面")
end_time = timeit.default_timer()
print("共爬取{}個 http 代理,可用{}個,爬取{}個 https 代理,可用{}個,總耗時{}s".format(Unhttp_proxy_num,
http_proxy_num, Unhttps_proxy_num, https_proxy_num, end_time-start_time))

提示:由於代理的時效性,不建議長期存儲,建議 “即用即爬”,另外請勿重複使用,短時間內使用兩三次就會封掉 ip,如果 ip 被封的話掛 vpn 即可。

效果展示#

image

image

image

可以看到免費的高匿 ip 的有效個數還是很少的,376 個 http 代理只有 53 個可用,409 和 https 代理只有 72 個可用。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。