前言#
為什麼要用代理? 不管是爆破還是爬蟲,經常會遇到 IP 被封的情況,此時,自建一個可靠的代理池就顯得很有必要了。
代理有哪些分類 以代理的匿名度來分,可以分為透明代理、匿名代理和高度匿名代理。
- 使用透明代理,對方伺服器可以知道你使用了代理,並且也知道你的真實 IP。
- 使用匿名代理,對方伺服器可以知道你使用了代理,但不知道你的真實 IP。
- 使用高匿名代理,對方伺服器不知道你使用了代理,更不知道你的真實 IP。
此篇文章就是爬取國內比較出名的西刺代理(還有一個快代理也比較出名,爬取步驟和這個差不多)的高匿代理並且驗證可用性。
思路#
獲取信息#
首先,我們需要用 requests 庫把網頁爬下來,然後查看我們需要的信息在哪個標籤頁裡。
可以看到每一個代理的所有信息都包含在一個 tr 標籤中,而每一個信息又都是放在一個 td 標籤中的:
那麼我們就可以使用 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 即可。
效果展示#
可以看到免費的高匿 ip 的有效個數還是很少的,376 個 http 代理只有 53 個可用,409 和 https 代理只有 72 個可用。