天天看點

Pandas一鍵爬取解析代理IP與代理IP池的維護

大家好,我是小小明,今天我計劃搭建一個代理IP池,采集一些公開的免費的代理IP,放入緩存池中。

要搭建一個代理ip池,我的思路:

  1. 爬蟲定期爬取代理IP,驗證代理iP有效性,有效則存入Redis資料庫
  2. 一個線程或程序定期檢查代理ip池的有效性,無效則從中删除

雖然不如直接購買付費的代理IP好用,但是以此訓練一下相關技術能力。

本文的目标是至少示範如下技術:

  1. pandas超簡代碼帶請求頭解析表格
  2. 檢視通路IP的方法
  3. 搭建簡易檢驗代理Ip的網站
  4. Redis資料庫的基本操作
  5. 代理ip在request庫中的使用方法
  6. Timer定時器的使用
  7. Redis 圖形化工具的使用介紹

代理IP的爬取與解析

下面,首先去選擇幾個免費代理ip的網址去爬一下,百度一下代理ip關鍵字能夠查到很多網站。

這裡我選擇崔老師書中推薦的一個代理網站進行測試,它提供了高匿代理和普通代理兩個頁面。

Pandas一鍵爬取解析代理IP與代理IP池的維護

爬取高匿測試一下:

import requests
import pandas as pd

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36'
}

i = 1
url = f'https://www.kuaidaili.com/free/inha/{i}/'
r = requests.get(url, headers=headers)
r.encoding = 'u8'
ip_df, = pd.read_html(r.text)      
Pandas一鍵爬取解析代理IP與代理IP池的維護

代理IP爬了,但是是否真的能用呢?

代理IP的校驗

我們需要驗證一下,有個不錯的測試網站是httpbin.org,我們可以通過它來讀取自己的ip位址:

r = requests.get(f"http://httpbin.org/ip")
r.json()      

結果大緻形式:

{'origin': '116.xx.xx.xxx'}      

然後以如下形式通路代理ip:

r = requests.get(f"http://httpbin.org/ip",
                 proxies={'http': "175.42.158.226:9999"})
r.json()      

若抛出異常:

Pandas一鍵爬取解析代理IP與代理IP池的維護

說明代理失效。

在我們的機器有公網IP時,我們也可以搭建自己的伺服器用于校驗代理IP有效性,flask代碼:

from flask import *

app = Flask(__name__)


@app.route('/')
def index():
    return request.remote_addr


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8088)      

基于此我們可以寫一個校驗代理IP的方法:

def proxie_ip_validity_check(proxie_ip, proxie_type='http', timeout=1):
    try:
        r = requests.get(f"{proxie_type}://httpbin.org/ip", headers=headers,
                         proxies={proxie_type: proxie_ip}, timeout=timeout)
        if r.status_code == 200:
            return True
        return False
    except Exception as e:
        return False      

經過我個人測試,高匿代理似乎沒有一個是可以用的,是以這次我們隻爬取普通代理ip。

批量爬取普通代理ip

設定逾時時間為2秒,取7頁資料:

import requests
import pandas as pd

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36'
}

http_ip_pool = []
https_ip_pool = []
for page in range(1, 8):
    print("目前爬取頁面數:", page)
    url = f'https://www.kuaidaili.com/free/intr/{page}'
    r = requests.get(url, headers=headers)
    r.encoding = 'u8'
    ip_df, = pd.read_html(r.text)
    for ip, port, proxie_type in ip_df[["IP", "PORT", "類型"]].values:
        pool = http_ip_pool if proxie_type.lower() == 'http' else https_ip_pool
        proxie_ip = f"{ip}:{port}"
        if proxie_ip_validity_check(proxie_ip, proxie_type.lower(), 2):
            pool.append(proxie_ip)
len(http_ip_pool), len(https_ip_pool)      

經測試爬到26個有效的代理ip,全部都是http類型:

Pandas一鍵爬取解析代理IP與代理IP池的維護

寫入到Redis伺服器

為了友善其他爬蟲程式使用,我們将爬到的代理IP寫入到Redis資料庫中。

首先需要安裝Redis,下載下傳位址:https://github.com/MicrosoftArchive/redis/releases

我的是Windows平台選擇了:https://github.com/microsoftarchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.zip

解壓後直接輕按兩下redis-server.exe即可啟動Redis伺服器:

Pandas一鍵爬取解析代理IP與代理IP池的維護

安裝讓Python操作redis的庫:

pip install redis      

基本使用:

import redis

r = redis.Redis(host='192.168.3.31', port=6379, db=0)
r.set('name', 'zhangsan')  # 添加
print(r.get('name'))      
b'zhangsan'      

考慮将代理IP存入Redis的set集合中,因為可以自動去重,測試一下:

r.sadd("proxie_ip", "ip1", "ip2")
r.sadd("proxie_ip", "ip2", "ip3")
r.smembers("proxie_ip")      
{b'ip1', b'ip2', b'ip3'}      

于是我們可以通過以下指令一次性存入Redis中:

if len(http_ip_pool)>0:
    r.sadd("proxie_ip_http", *http_ip_pool)
if len(https_ip_pool)>0:
    r.sadd("proxie_ip_https", *https_ip_pool)      

存入Redis資料庫後,Redis就已經相當于一個代理IP池,下面我們另外啟動一個嘗試,常識使用這些代理IP:

從代理IP池中擷取代理IP并使用

讀取代理IP:

import redis

with redis.Redis(host='192.168.3.31', port=6379, db=0) as r:
    http_ip_bytes = r.smembers("proxie_ip_http")
    http_ip_pool = list(map(bytes.decode, http_ip_bytes))      
['139.9.25.69:3128',
 '119.3.235.101:3128',
 '122.51.207.244:8888',
 '106.58.191.24:8888',
 '222.74.202.229:9999',
 '47.111.71.29:8081',
 '222.74.202.229:80',
 '222.74.202.244:80',
 '106.58.191.218:8888',
 '222.74.202.229:8080']      

然後可以拿來模拟爬百度:

import requests
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36'
}

for ip in http_ip_pool:
    try:
        r = requests.get(f"http://www.baidu.com/",
                         headers=headers, proxies={'http': ip}, timeout=1)
        print(r.status_code)
    except Exception as e:
        print(e)      
HTTPConnectionPool(host='139.9.25.69', port=3128): Max retries exceeded with url: http://www.baidu.com/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000028D6A993248>, 'Connection to 139.9.25.69 timed out. (connect timeout=1)'))
200
200
HTTPConnectionPool(host='106.58.191.24', port=8888): Max retries exceeded with url: http://www.baidu.com/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000028D6B59C2C8>, 'Connection to 106.58.191.24 timed out. (connect timeout=1)'))
200
HTTPConnectionPool(host='47.111.71.29', port=8081): Max retries exceeded with url: http://www.baidu.com/ (Caused by ProxyError('Cannot connect to proxy.', RemoteDisconnected('Remote end closed connection without response')))
200
200
HTTPConnectionPool(host='106.58.191.218', port=8888): Max retries exceeded with url: http://www.baidu.com/ (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x0000028D6A9C2F88>, 'Connection to 106.58.191.218 timed out. (connect timeout=1)'))
200      

可以看到隻有一半的代理IP能在指定時間内成功通路百度,說明免費的代理IP就是不如收費的穩定。

定期爬取并清除失效的代理IP

隻要再完成這部分,我們的代理IP池就能夠初見雛形,沖!

最終完成代碼:

import requests
import pandas as pd
import redis
from threading import Timer
import time

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36'
}


def proxie_ip_validity_check(proxie_ip, proxie_type='http', timeout=2):
    try:
        r = requests.get(f"{proxie_type}://httpbin.org/ip",
                         proxies={proxie_type: proxie_ip}, timeout=timeout)
        if r.status_code == 200:
            return True
        return False
    except:
        return False


redis_conn = redis.Redis(host='192.168.3.31', port=6379)


def crawl_proxy_ip2redis():
    for page in range(1, 20):
        print("目前爬取頁面:", page)
        url = f'https://www.kuaidaili.com/free/intr/{page}'
        r = requests.get(url, headers=headers)
        r.encoding = 'u8'
        ip_df, = pd.read_html(r.text)
        for ip, port, proxie_type in ip_df[["IP", "PORT", "類型"]].values:
            proxie_ip = f"{ip}:{port}"
            proxie_type = proxie_type.lower()
            if proxie_ip_validity_check(proxie_ip, proxie_type, 2):
                redis_conn.sadd(f"proxie_ip_{proxie_type}", proxie_ip)


def get_proxy_ip_pool(proxie_type='http'):
    http_ip_bytes = redis_conn.smembers(f"proxie_ip_{proxie_type}")
    http_ip_pool = list(map(bytes.decode, http_ip_bytes))
    return http_ip_pool


def clear_invalid_proxie_ip():
    print("開始清空失效代理ip:")
    for proxie_type in ['http', 'https']:
        for ip in get_proxy_ip_pool(proxie_type):
            if not proxie_ip_validity_check(ip, proxie_type, 2):
                print(proxie_type, ip, "失效")
                redis_conn.srem(f"proxie_ip_{proxie_type}", ip)


crawl_proxy_ip2redis()
while True:
    # 5分鐘後清空一次失效的代理ip
    Timer(5 * 60, clear_invalid_proxie_ip, ()).start()
    # 10分鐘後下載下傳一次代理ip
    Timer(10 * 60, crawl_proxy_ip2redis, ()).start()
    # 每隔10分鐘一個周期
    time.sleep(10 * 60)      

目前測試,這個Redis代理IP池還算好使,作為一個雛形勉強能用。

可以通過Redis圖形化工具檢視代理IP池的存儲情況:

Pandas一鍵爬取解析代理IP與代理IP池的維護