scrapy-redis搭配splash全站爬虫

上一次爬取BOSS直聘,我高调地在领英上发了几个帖子,结果招来了BOSS直聘一个高管的访问,还好我只爬了100多万职位数据,每天最多爬取8-10万,估计没有给对方服务器带来太大压力,不然我可能因为一时兴起惹了麻烦。

爬取BOSS直聘时我是自建的框架,最关键的突破当然是跨越了selenium阻塞影响并发这道坎儿。实际上,很多情况下设置下Desired_Capability中的page_load policy为’None’,selenium几乎就成了非阻塞了。当然,这个小秘密初学者一般不知道,大都会抱怨selenium的慢。我测试过,采用这种策略,不到1秒就能返回页面数据,印象中应该是几十到几百毫秒吧。

即便如此,我还是没能完美解决selenium超时这个问题,毕竟是模拟浏览器,它在超时的时候往往会停止响应,阻塞下面的流程,怎么设置timeout参数都没用。后来我发现,多进程会稳定很多,多线程timeout带来的阻塞问题在多进程并不是太大的问题,一是线程独立,挂掉一个互不影响,二是如果真的阻塞了可以根据pid来杀死进程重启。当然,我没有用scrapy搭配selenium,以后有机会可以试试。

BOSS直聘的反爬还是有点水平的,主要是它很容易弹出验证码。我用过多贝云和阿布云的隧道代理,两家质量差不多,都不错,只是动态版的IP响应速度会慢一些。问题是每个IP用在BOSS直聘效果不一样,有的估计之前被人用过所以第一次就被BOSS黑了,好点的话一般短时间内爬取好像20页问题不大。当然我还用ADSL自建过代理池,效果一般,主要是ADSL主机只有两个,池子小了容易造成程序等待IP代理。

这次爬取拉勾网,我才用了新的框架,scrapy-redis-splash,研究了几天就上手了。说实话,写这种程序和平时处理分析地震数据思维是一样的,只是工具不同而已。找出问题-问题分解-解决问题,都是这么一个思路。总结下这个框架吧。

  1. scrapy: 成熟框架,容易上手,但是因为是框架,需要多读文档多读源码才行,不是自建的轮子用起来不是很顺手,毕竟不能控制主循环。
  2. redis:因为之前做代理池就用过,这次用起来也很顺手。redis去重和调度并不像文档说的那么简单,比如爬虫重启时会有个问题,硬重启需要删除之前的requests,问题是之前的url虽然爬取过进了dupefilter,但是并没有成功爬取Item,重启后这些就会被过滤了。这个应该不难解决,我有时间看看源码。
  3. splash。这个才是重头戏,splash有个问题就是不释放内存,即使我的笔记本有32G内存也会很快被耗尽,虽然代码中我做了内存监控重启docker,也不是很完美。这个还是小问题,代码中可以监控或者启动docker时限制内存使用即可,大问题是504 gateway timeout,官方文档中和网络上说的设置一个很大的timeout实际上不好使,504照样还是很多,而且我发现只要是504就会阻塞,设置过大了程序有时就卡住了,太小了504就更多。504告诉我们的实际上就是splash没能及时渲染出网页,这个的终极解决方案是HAproxy做一个load balancer,用多个docker instance(2G内存一个都可以),甚至自己的笔记本都能并发多个实例。这里我用了Acquarium, github上有,Acquarium唯一的坑就是在Docker for windows下你可能无法登录8036那个监控页面,这里涉及到了Docker和宿主机的网络问题,比较复杂,启用ubuntu WSL2 backend就好了,然后就能访问127.0.0.1:8036来看负载均衡的状态监控了。
  4. 再说说拉勾网吧,难度很低,几乎没有反爬,甚至不限制IP访问频率!当然,你不能调用网络上盛传的那个端口来获取json,况且那个接口只能获取列表页,不能获取详情页。
  5. IP变化少的情况下要考虑网站的CDN,如果不用多地区的不同IP,爬虫速度很难上去,毕竟CDN节点会有限速啊!

熟悉了框架,就能用它来爬取高难度的网站了。目前计划爬取的几个目标:

  1. 京东。全站爬取估计很费劲,京东自称有500多万sku,京东虽然是树形结构,但是这么多商品肯定要细分类才能爬到更多数据,URL去重无法避免,不同组合之间的元素会有交集啊。因为是电商,还需要它的评论数据,很多热门商品都是几万几十万评论,爬起来也很费时。当然,之前我做过京东商品抢购的脚本,虽然没能抢到茅台,但是能用到这个爬虫中。
  2. 拼多多。电商中的黑马,虽然我从来不用。
  3. 唯品会,媳妇儿的最爱,怎能不光顾下?
  4. 携程,去哪儿,爱彼迎。爱旅游的我自然对他们感兴趣
  5. 出行必备滴滴。这个得看看它的APP,看看有没有接口,估计难度不小。
  6. 12306,爬虫们的最爱。
  7. 小红书,潮人们的必备APP
  8. 抖音,快手,虽然我从来不用,也没有装。刷视频比追剧还要浪费时间
  9. 学习强国,这个PC端我已经写好了,APP端因为目前要用HyperV(docker),不太方便使用安卓模拟器。还需要一个题库API,有时间我做下

不说了,这些够我忙活大半年了。边爬边分析吧。

拉勾网爬虫截屏,单机4进程*10线程, 6个splash instance,200KB/s – 1MB/s,平均2.5个职位/秒

Docker scrapy redis spalsh 爬虫踩坑

首先说个结论,很多问题都是墙引起的.

1.Docker如果不设置好proxy,用起来会非常痛苦.我用了Proxychains + 本地socks5代理,不然速度慢的让你有种被虐的感觉.当然,临时用也可以用export的方式,比如Ubuntu中可以用sudo apt-get -o Acquire::socks::proxy=”socks://127.0.0.1:8399/” update

2.Docker splash安装后会发现在Web端无法渲染有些网站,这里也是Github源码中用了一些境外的js,这些被墙了.对,没错,这些也能被墙.下面链接可以参考,主要是替换resources.py中的两个文件http://libs.baidu.com/jquery/1.11.1/jquery.min.js, https://ajax.aspnetcdn.com/ajax/jquery.migrate/jquery-migrate-1.2.1.js

docker ps

docker exec -u 0 -it ed39a8b02925 /bin/bash

https://blog.csdn.net/qq_42078965/article/details/108812636

说到代理,下面的代码适合socks5代理github,不然访问github太慢了:

git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

3. WSL内存管理有问题,splash运行一段时间机器内存就会炸掉。目前的workaround是用脚本监控系统内存并重启container:

import psutil
import os 
import time
import subprocess

def id_find():
    tmp = str(subprocess.check_output('sudo docker ps', shell=True)).split('\\n')
    print(tmp)
    if tmp[1] ==  "'":
        print('no container_id exists')
        return 0
    else:        
        print('one container_id found')
        container_id = tmp[1].split(' ')[0]
        print('container_id 1:', container_id)
        return container_id    
    
while True:
    mem = psutil.virtual_memory()
    total = str(round(mem.total / 1024 / 1024))
    #round方法进行四舍五入,然后转换成字符串 字节/1024得到kb 再/1024得到M
    used = str(round(mem.used / 1024 / 1024))
    use_per = str(round(mem.percent))
    free = str(round(mem.free / 1024 / 1024))
    print("您当前的内存大小为:" + total + "M")
    print("已使用:" + used + "M(" + use_per + "%)")
    print("可用内存:" + free + "M")
    container_id = id_find()
    print('container_id return:', container_id)
    if container_id == 0:        
        time.sleep(3)
        os.system('sudo docker run -itd -v ~/default.ini -p 8050:8050 scrapinghub/splash /bin/bash --max-timeout=3600')
        time.sleep(5)
        # os.system('sudo docker container prune') # remove all stopped containers. CAUTION!!!!
    else:
        print('one container_id found: ', container_id)
        if int(use_per) > 90:
            os.system(f'sudo docker restart {container_id}')
            # os.system(f'sudo docker run -itd -v lagoucrawl\default.ini -p 8050:8050 scrapinghub/splash /bin/bash --max-timeout=600')
            time.sleep(3)
            # os.system('sudo docker container prune') # remove all stopped containers. CAUTION!!!!
        else:
            time.sleep(3)

这里的default.ini是我的多贝云代理文件,内容如下:

[proxy]
   
host=http-proxy-t1.dobel.cn
port=9180

username=BOSSZxxxxxxx
password=yyyyyyy

因为我用了scrapy-redis-splash这个框架,不能直接套用scrapy代理的方式,实践证明这个是有效也是最简单的splash proxy方法。

说到这儿,顺便说下docker的加速(以阿里云为例):

{
  "registry-mirrors": [
    "https://qbsssss.mirror.aliyuncs.com"
  ],
  "insecure-registries": [],
  "debug": false,
  "experimental": false,
  "features": {
    "buildkit": true
  }
}

上面的连接登录阿里云之后能找到,Google下即可。这里有个坑,如果你用国外阿里云账号登录,是死活找不到这个链接的!

因为GFW的缘故,国内很多技术人员获取最先进的技术资料是很困难的。仅仅说proxy,不同的平台设置都不一样,github, wget, docker。。。。。。即使是proxychains也不是一站式解决方案。

4. Redis看起来简单,也是很多坑需要踩的。比如,强制停止爬虫程序后重启之前可能需要删除redis中的requests,不然爬虫程序可能无法继续。