阅读 22

最新实用Python异步爬虫代理池(开源)

--  Illustrations by Tom Haugomat --


陈键冬

Python中文社区专栏作者,pyecharts开源项目核心开发者。

GitHub:chenjiandongx

\



项目地址

复制代码
  1. https://github.com/chenjiandongx/async-proxy-pool

**
**

Async Proxy Pool\

异步爬虫代理池,以 Python asyncio 为基础,旨在充分利用 Python 的异步性能。

运行环境

项目使用了 sanic,一个异步网络框架。所以建议运行 Python 环境为 Python3.5+,并且 sanic 不支持 Windows 系统,Windows 用户(比如我 smile)可以考虑使用 Ubuntu on Windows。

如何使用

安装 Redis

项目数据库使用了 Redis,Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。所以请确保运行环境已经正确安装了 Redis。安装方法请参照官网指南。

下载项目源码

复制代码
  1. $ git clone https://github.com/chenjiandongx/async-proxy-pool.git

安装依赖

使用 requirements.txt

复制代码
  1. $ pip install -r requirements.txt

使用 pipenv Pipfile

复制代码
  1. $ pipenv install

配置文件

配置文件 config.py,保存了项目所使用到的所有配置项。如下所示,用户可以根据需求自行更改。不然按默认即可。

复制代码
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. ``
  4. # 请求超时时间(秒)
  5. REQUEST_TIMEOUT = 15
  6. # 请求延迟时间(秒)
  7. REQUEST_DELAY = 0
  8. ``
  9. # redis 地址
  10. REDIS_HOST = "localhost"
  11. # redis 端口
  12. REDIS_PORT = 6379
  13. # redis 密码
  14. REDIS_PASSWORD = None
  15. # redis set key
  16. REDIS_KEY = "proxies"
  17. # redis 连接池最大连接量
  18. REDIS_MAX_CONNECTION = 20
  19. ``
  20. # REDIS SCORE 最大分数
  21. MAX_SCORE = 10
  22. # REDIS SCORE 最小分数
  23. MIN_SCORE = 0
  24. # REDIS SCORE 初始分数
  25. INIT_SCORE = 9
  26. ``
  27. # sanic web host
  28. SANIC_HOST = "localhost"
  29. # sanic web port
  30. SANIC_PORT = 3289
  31. # 是否开启 sanic 日志记录
  32. SANIC_ACCESS_LOG = True
  33. ``
  34. # 批量测试数量
  35. VALIDATOR_BATCH_COUNT = 256
  36. # 校验器测试网站,可以定向改为自己想爬取的网站,如新浪,知乎等
  37. VALIDATOR_BASE_URL = "https://httpbin.org/"
  38. # 校验器循环周期(分钟)
  39. VALIDATOR_RUN_CYCLE = 15
  40. ``
  41. ``
  42. # 爬取器循环周期(分钟)
  43. CRAWLER_RUN_CYCLE = 30
  44. # 请求 headers
  45. HEADERS = {
  46.    "X-Requested-With": "XMLHttpRequest",
  47.    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
  48.    "(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
  49. }

运行项目 运行客户端,启动收集器和校验器

复制代码
  1. # 可设置校验网站环境变量 set/export VALIDATOR_BASE_URL="https://example.com"
  2. $ python client.py
  3. 2018-05-16 23:41:39,234 - Crawler working...
  4. 2018-05-16 23:41:40,509 - Crawler √ http://202.83.123.33:3128
  5. 2018-05-16 23:41:40,509 - Crawler √ http://123.53.118.122:61234
  6. 2018-05-16 23:41:40,510 - Crawler √ http://212.237.63.84:8888
  7. 2018-05-16 23:41:40,510 - Crawler √ http://36.73.102.245:8080
  8. 2018-05-16 23:41:40,511 - Crawler √ http://78.137.90.253:8080
  9. 2018-05-16 23:41:40,512 - Crawler √ http://5.45.70.39:1490
  10. 2018-05-16 23:41:40,512 - Crawler √ http://117.102.97.162:8080
  11. 2018-05-16 23:41:40,513 - Crawler √ http://109.185.149.65:8080
  12. 2018-05-16 23:41:40,513 - Crawler √ http://189.39.143.172:20183
  13. 2018-05-16 23:41:40,514 - Crawler √ http://186.225.112.62:20183
  14. 2018-05-16 23:41:40,514 - Crawler √ http://189.126.66.154:20183
  15. ...
  16. 2018-05-16 23:41:55,866 - Validator working...
  17. 2018-05-16 23:41:56,951 - Validator × https://114.113.126.82:80
  18. 2018-05-16 23:41:56,953 - Validator × https://114.199.125.242:80
  19. 2018-05-16 23:41:56,955 - Validator × https://114.228.75.17:6666
  20. 2018-05-16 23:41:56,957 - Validator × https://115.227.3.86:9000
  21. 2018-05-16 23:41:56,960 - Validator × https://115.229.88.191:9000
  22. 2018-05-16 23:41:56,964 - Validator × https://115.229.89.100:9000
  23. 2018-05-16 23:41:56,966 - Validator × https://103.18.180.194:8080
  24. 2018-05-16 23:41:56,967 - Validator × https://115.229.90.207:9000
  25. 2018-05-16 23:41:56,968 - Validator × https://103.216.144.17:8080
  26. 2018-05-16 23:41:56,969 - Validator × https://117.65.43.29:31588
  27. 2018-05-16 23:41:56,971 - Validator × https://103.248.232.135:8080
  28. 2018-05-16 23:41:56,972 - Validator × https://117.94.69.166:61234
  29. 2018-05-16 23:41:56,975 - Validator × https://103.26.56.109:8080
  30. ...

运行服务器,启动 web 服务

复制代码
  1. $ python server.py
  2. [2018-05-16 23:36:22 +0800] [108] [INFO] Goin' Fast @ http://localhost:3289
  3. [2018-05-16 23:36:22 +0800] [108] [INFO] Starting worker [108]

总体架构

项目主要几大模块分别是爬取模块,存储模块,校验模块,调度模块,接口模块。

  • 爬取模块 负责爬取代理网站,并将所得到的代理存入到数据库,每个代理的初始化权值为 INIT_SCORE。

  • 存储模块 封装了 Redis 操作的一些接口,提供 Redis 连接池。

  • 校验模块 验证代理 IP 是否可用,如果代理可用则权值 +1,最大值为 MAX_SCORE。不可用则权值 -1,直至权值为 0 时将代理从数据库中删除。

  • 调度模块 负责调度爬取器和校验器的运行。

  • 接口模块 使用 sanic 提供 WEB API 。

    \

/

欢迎页面

复制代码
  1. $ http http://localhost:3289/
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 42
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. {
  9.    "Welcome": "This is a proxy pool system."
  10. }

/pop

随机返回一个代理,分三次尝试。

  1. 尝试返回权值为 MAX_SCORE,也就是最新可用的代理。
  2. 尝试返回随机权值在 (MAXSCORE -3) - MAXSCORE 之间的代理。
  3. 尝试返回权值在 0 - MAX_SCORE 之间的代理
复制代码
  1. $ http http://localhost:3289/pop
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 38
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. {
  9.    "http": "http://46.48.105.235:8080"
  10. }

/get/<count:int>

返回指定数量的代理,权值从大到小排序。

复制代码
  1. $ http http://localhost:3289/get/10
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 393
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. [
  9.    {
  10.        "http": "http://94.177.214.215:3128"
  11.    },
  12.    {
  13.        "http": "http://94.139.242.70:53281"
  14.    },
  15.    {
  16.        "http": "http://94.130.92.40:3128"
  17.    },
  18.    {
  19.        "http": "http://82.78.28.139:8080"
  20.    },
  21.    {
  22.        "http": "http://82.222.153.227:9090"
  23.    },
  24.    {
  25.        "http": "http://80.211.228.238:8888"
  26.    },
  27.    {
  28.        "http": "http://80.211.180.224:3128"
  29.    },
  30.    {
  31.        "http": "http://79.101.98.2:53281"
  32.    },
  33.    {
  34.        "http": "http://66.96.233.182:8080"
  35.    },
  36.    {
  37.        "http": "http://61.228.45.165:8080"
  38.    }
  39. ]

/count

返回代理池中所有代理总数

复制代码
  1. $ http http://localhost:3289/count
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 15
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. {
  9.    "count": "698"
  10. }

/count/<score:int>

返回指定权值代理总数

复制代码
  1. $ http http://localhost:3289/count/10
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 15
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. {
  9.    "count": "143"
  10. }

/clear/<score:int>

删除权值小于等于 score 的代理

复制代码
  1. $ http http://localhost:3289/clear/0
  2. HTTP/1.1 200 OK
  3. Connection: keep-alive
  4. Content-Length: 22
  5. Content-Type: application/json
  6. Keep-Alive: 5
  7. ``
  8. {
  9.    "Clear": "Successful"
  10. }

扩展爬取网站

在 crawler.py 文件里新增你自己的爬取方法。

复制代码
  1. class Crawler:
  2. ``
  3.    @staticmethod
  4.    def run():
  5.        ...
  6. ``
  7.    # 新增你自己的爬取方法
  8.    @staticmethod
  9.    @collect_funcs      # 加入装饰器用于最后运行函数
  10.    def crawl_xxx():
  11.        # 爬取逻辑

sanic 性能测试

使用 wrk 进行服务器压力测试。基准测试 30 秒, 使用 12 个线程, 并发 400 个 http 连接。

测试 http://127.0.0.1:3289/pop

复制代码
  1. $ wrk -t12 -c400 -d30s http://127.0.0.1:3289/pop
  2. Running 30s test @ http://127.0.0.1:3289/pop
  3.  12 threads and 400 connections
  4.  Thread Stats   Avg      Stdev     Max   +/- Stdev
  5.    Latency   350.37ms  118.99ms 660.41ms   60.94%
  6.    Req/Sec    98.18     35.94   277.00     79.43%
  7.  33694 requests in 30.10s, 4.77MB read
  8.  Socket errors: connect 0, read 340, write 0, timeout 0
  9. Requests/sec:   1119.44
  10. Transfer/sec:    162.23KB

测试 http://127.0.0.1:3289/get/10

复制代码
  1. Running 30s test @ http://127.0.0.1:3289/get/10
  2.  12 threads and 400 connections
  3.  Thread Stats   Avg      Stdev     Max   +/- Stdev
  4.    Latency   254.90ms   95.43ms 615.14ms   63.51%
  5.    Req/Sec   144.84     61.52   320.00     66.58%
  6.  46538 requests in 30.10s, 22.37MB read
  7.  Socket errors: connect 0, read 28, write 0, timeout 0
  8. Requests/sec:   1546.20
  9. Transfer/sec:    761.02KB

性能还算不错,再测试一下没有 Redis 操作的 http://127.0.0.1:3289/

复制代码
  1. $ wrk -t12 -c400 -d30s http://127.0.0.1:3289/
  2. Running 30s test @ http://127.0.0.1:3289/
  3.  12 threads and 400 connections
  4.  Thread Stats   Avg      Stdev     Max   +/- Stdev
  5.    Latency   127.86ms   41.71ms 260.69ms   55.22%
  6.    Req/Sec   258.56     92.25   520.00     68.90%
  7.  92766 requests in 30.10s, 13.45MB read
  8. Requests/sec:   3081.87
  9. Transfer/sec:    457.47KB

Requests/sec: 3081.87

关闭 sanic 日志记录,测试 http://127.0.0.1:3289/

复制代码
  1. $ wrk -t12 -c400 -d30s http://127.0.0.1:3289/
  2. Running 30s test @ http://127.0.0.1:3289/
  3.  12 threads and 400 connections
  4.  Thread Stats   Avg      Stdev     Max   +/- Stdev
  5.    Latency    34.63ms   12.66ms  96.28ms   58.07%
  6.    Req/Sec     0.96k   137.29     2.21k    73.29%
  7.  342764 requests in 30.10s, 49.69MB read
  8. Requests/sec:  11387.89
  9. Transfer/sec:      1.65MB

Requests/sec: 11387.89

实际代理性能测试

test_proxy.py 用于测试实际代理性能

运行代码

复制代码
  1. $ cd test
  2. $ python test_proxy.py
  3. ``
  4. # 可设置的环境变量
  5. TEST_COUNT = os.environ.get("TEST_COUNT") or 1000
  6. TEST_WEBSITE = os.environ.get("TEST_WEBSITE") or "https://httpbin.org/"
  7. TEST_PROXIES = os.environ.get("TEST_PROXIES") or "http://localhost:3289/get/20"

实测效果

https://httpbin.org/

复制代码
  1. 测试代理: http://localhost:3289/get/20
  2. 测试网站: https://httpbin.org/
  3. 测试次数: 1000
  4. 成功次数: 1000
  5. 失败次数: 0
  6. 成功率: 1.0

https://taobao.com

复制代码
  1. 测试代理: http://localhost:3289/get/20
  2. 测试网站: https://taobao.com/
  3. 测试次数: 1000
  4. 成功次数: 984
  5. 失败次数: 16
  6. 成功率: 0.984

https://baidu.com

复制代码
  1. 测试代理: http://localhost:3289/get/20
  2. 测试网站: https://baidu.com
  3. 测试次数: 1000
  4. 成功次数: 975
  5. 失败次数: 25
  6. 成功率: 0.975

https://zhihu.com

复制代码
  1. 测试代理: http://localhost:3289/get/20
  2. 测试网站: https://zhihu.com
  3. 测试次数: 1000
  4. 成功次数: 1000
  5. 失败次数: 0
  6. 成功率: 1.0

可以看到其实性能是非常棒的,成功率极高。 wink

实际应用示例

复制代码
  1. import random
  2. ``
  3. import requests
  4. ``
  5. # 确保已经启动 sanic 服务
  6. # 获取多个然后随机选一个
  7. ``
  8. try:
  9.    proxies = requests.get("http://localhost:3289/get/20").json()
  10.    req = requests.get("https://example.com", proxies=random.choice(proxies))
  11. except:
  12.    raise
  13. ``
  14. # 或者单独弹出一个
  15. ``
  16. try:
  17.    proxy = requests.get("http://localhost:3289/pop").json()
  18.    req = requests.get("https://example.com", proxies=proxy)
  19. except:
  20.    raise

aiohttp 的坑

整个项目都是基于 aiohttp 这个异步网络库的,在这个项目的文档中,关于代理的介绍是这样的。

划重点:aiohttp supports HTTP/HTTPS proxies

但是,它根本就不支持 https 代理好吧,在它的代码中是这样写的。

划重点:Only http proxies are supported

我的心情可以说是十分复杂的。astonished 不过只有 http 代理效果也不错没什么太大影响,参见上面的测试数据。

参考借鉴项目

  • ProxyPool
  • proxy_pool

License

MIT ©chenjiandongx


Python中文社区

全球Python中文开发者的

精神部落

Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、极客邦、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以公安部、工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。

\

最近热门文章

用Python分析苹果公司股价数据\

Nginx+uwsgi部署Django应用\

用文本挖掘剖析近5万首《全唐诗》\

Python自然语言处理分析倚天屠龙记\

Python 3.6实现单博主微博文本、图片及热评爬取

点击下方****阅读原文 免费成为****社区会员

文章分类
后端
文章标签