ip代理池的爬蟲編寫、驗證和維護


打算法比賽有點累,比賽之余寫點小項目來提升一下工程能力、順便陶冶一下情操
本來是想買一個服務器寫個博客或者是弄個什么翻牆的東西
最后刷知乎看到有一個很有意思的項目,就是維護一個「高可用低延遲的高匿IP代理池」
於是就想自己把這個項目寫一次,其中有些更改,有些沒有實現
(數據結構作業要寫廣義表,寫項目時發現還沒寫 :)

原知乎鏈接:https://www.zhihu.com/question/47464143 (作者:resolvewang)
原項目github鏈接:https://github.com/SpiderClub/haipproxy
在此感謝原作者 resolvewang

本項目鏈接:https://github.com/TangliziGit/proxypool

項目早已在服務器上運行,這篇隨筆就是未完待續吧
咕咕咕,鴿了

大體思路

  • 用爬蟲爬下網絡上的免費代理ip
  • 對爬取的代理ip進行驗證,過濾掉一些不可用、低速的、有網頁跳轉的代理
  • 編寫調度器,對各個網站定時爬取、驗證免費代理;並對數據庫中以爬取的代理進行驗證
  • 寫一個web api,提供數據庫中已有的代理ip

大體框架


(我就只是想畫個圖而已 -_-)

項目第一階段目錄:

proxypool/
├── booter.py
├── dump.rdb
├── place.txt
├── proxypool
│   ├── __init__.py
│   ├── items.py
│   ├── logger.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── rules.py
│   ├── settings.py
│   ├── spiders
│   │   ├── base_spider.py
│   │   ├── common_spider.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   ├── task_queue.py
│   ├── user_agent.py
│   └── validators
│       ├── baidu_validator.py
│       ├── base_validator.py
│       ├── __init__.py
│       ├── __pycache__
│       └── zhihu_validator.py
├── __pycache__
├── scheduler.py
├── scrapy.cfg
└── testip.py

7 directories, 24 files

// wc -l `find . -name "*.py"`
    5 ./booter.py
  119 ./proxypool/middlewares.py
    5 ./proxypool/spiders/common_spider.py
   67 ./proxypool/spiders/base_spider.py
    9 ./proxypool/spiders/__init__.py
    7 ./proxypool/logger.py
   91 ./proxypool/settings.py
   11 ./proxypool/user_agent.py
   86 ./proxypool/rules.py
   57 ./proxypool/task_queue.py
    7 ./proxypool/validators/zhihu_validator.py
    7 ./proxypool/validators/baidu_validator.py
   56 ./proxypool/validators/base_validator.py
    5 ./proxypool/validators/__init__.py
   55 ./proxypool/pipelines.py
   21 ./proxypool/items.py
    0 ./proxypool/__init__.py
   59 ./scheduler.py
  667 total

細節描述

爬蟲部分

數據流向
RedisTaskQueue獲取鏈接
Spider發出請求
RandomUserAgentMiddware換UA
通過規則解析response
送至RedisRawProxyPipeline,未處理數據存入數據庫

  1. 爬取規則的編寫
    很多免費代理網站的結構都很相似,基本上就是這樣的(取自西刺代理):
  <tr class="odd">
    <td class="country"><img src="http://fs.xicidaili.com/images/flag/cn.png" alt="Cn" /></td>
    <td>223.240.209.18</td>
    <td>18118</td>
    <td>安徽合肥</td>
    <td class="country">高匿</td>
    <td>HTTP</td>
      <td>1分鍾</td>
    <td>不到1分鍾</td>
  </tr>
  <tr class="">
    <td class="country"><img src="http://fs.xicidaili.com/images/flag/cn.png" alt="Cn" /></td>
    <td>183.23.73.49</td>
    <td>61234</td>
    <td>廣東東莞</td>
    <td class="country">高匿</td>
    <td>HTTPS</td>
      <td>1小時</td>
    <td>不到1分鍾</td>
  </tr>
...

通過編寫爬取規則,我們就可以很方便爬取多個網站:

RULES = {
    "xici" : {
        "parser_type": "page",
        "prefix": "//tr",
        "detail": "td/text()",
        ...
    }
}

然后就可以類似這樣做請求:

[x.xpath(rule["detail"]) for x in response.xpath(rule["prefix"])]
  1. 設計RedisTaskQueue類,讓爬蟲從中取得要爬取的網站
    為啥不讓爬蟲自己從數據庫里取任務呢?
    呃 這個本來是為了多進程做的考慮,但是發現scrapy的Spider已經滿足時間上的需求了
    考慮到以后可能需要這個類來讓調度器調度爬蟲,於是就留下來了

  2. 設計基本爬蟲BaseSpider
    主要是以后用來做爬蟲種類的拓展,比如這個網頁可能會用js做個動態加載
    后續就要考慮到編寫JsSpider(BaseSpider)
    目前只有一個爬蟲CommonSpider(BaseSpider),用來爬普通網頁(普通網頁或json)

  3. Scrapy框架方面
    RawProxyUrlItem, ProxyUrlItem
    RandomUserAgentMiddleware, TimerMiddleware
    RedisRawProxyPipeline, RedisProxyPipeline

驗證部分

數據流向
RedisProxyQueue獲取ip
Spider發出驗證請求
TimerMiddleware開始計時
TimerMiddleware結束計時
通過規則驗證response
驗證通過,送至RedisRawProxyPipeline,驗證后ip存入數據庫

  1. 驗證規則
    與爬取規則相同,我們可選許多網站來做驗證(每個代理對各網站有不同的效率)
    為了方便管理,寫驗證規則
    為什么要驗證?
    一是為了保證代理速度
    二是為了保證不會存在“調包”的情況(中間人偷偷改了回復)

  2. 代理記分方式
    簡單的用請求時間來作為分數,存入Redis的有序集合

數據庫部分

數據項 描述
ProxyPool:RAW_IPPOOL 集合 存儲未驗證ip
ProxyPool:IPPOOL 有序集合 存儲驗證通過ip 按分數排序
ProxyPool:TASK_QUEUE 調度器暫時存入請求鏈接

調度器部分

這部分未完待續
僅僅寫了獲取爬蟲和驗證爬蟲的簡單啟動
下一步是根據爬取規則的時間間隔來調度

WebAPI部分

這部分根本還沒寫
不過這是項目里最簡單的東西
准備適當時間入一個服務器,用Flask簡單寫一寫就好了


總結要點

  1. 在項目里專門寫一個配置文件,用以配置工程內所有信息,避免hardcode
  2. 未來可能需要更多相似的類時,編寫基類是必須的,考慮到方便編寫和復用性
  3. 給類中添加某一功能時,如果項目較復雜,寫Mixin合適一點
  4. 若對大量(或后續可能大量)的網站做爬取時,最好抽象出爬取規則,便於處理添加更多爬取網站、更改爬取數據順序等
  5. 驗證代理ip,考慮代理速度和中間人“調包”的可能
  6. 使用無表的數據庫(such as Redis)時,為了結構清晰,將鍵值寫成"XXX:A:B"的形式

實現細節&需要注意的

  1. 每一個scrapy.Spider里可以自定義設置
  2. 比如設置pipeline, middleware, DOWNLOAD_DELAY
    custom_settings = {
        'DOWNLOAD_TIMEOUT': 1,
        'CONCURRENT_REQUESTS': 50,
        'CONCURRENT_REQUESTS_PER_DOMAIN': 50,
        'RETRY_ENABLED': False,
        'DOWNLOADER_MIDDLEWARES': {
            'proxypool.middlewares.TimerMiddleware': 500,
        },
        'ITEM_PIPELINES': {
            'proxypool.pipelines.RedisProxyPipeline': 200,
        }
    }
  1. Python取數據庫的數據后,要看看是不是byte類型
  2. scrapy.Request包括errback, dont_filter等很有用的參數
  3. scrapy通過CrawlerProcess方法不能重復啟動爬蟲,如有需要,用多進程即可

**未完待續**


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM