一些闲话
接下来将近两周没有课,我就帮一位同学写了个bilibili爬虫爬取十万条用户数据,来完成TA的大数据分析作业。原本以为用以前的日语听力习题爬虫改一改就能用,结果……B站果然是B站啊,反爬虫的本事还是相当高的。
先上最终完成版的源代码,注释应该够详细了:
|
|
代码背后的故事
咋一看很简单,用的知识点都很初级。项目这东西从来都是说着容易做着难,为了这150多行代码我也折腾了三天。
初期似乎很顺利
B站的UID排列很规律——从0排到两亿多,理论上我只要遍历就行。
原本不想用无头浏览器————效率还是太低。可只利用Beautifulsoup
, requests
, urllib
什么的,即使设置文件请求头,B站给我返回的页面是“您的浏览器不支持访问个人主页,请升级浏览器”。用无头浏览器没有这个问题。原因以后可以深究。
最开始的版本是单线程的,比起久远的日语听力题爬虫就多了个WebDriverWait
隐式等待页面加载完成,稍微提高一点效率。某天深夜爬取1000条数据用了1480秒,寻思着这也太慢了点,第二天就开始折腾多进程。
这个爬虫之前我也没写过多进程。俗话说得好,“人生苦短,我用python”,我看着廖雪峰的Python3
教程直接上手做,除了在这里传参时搞错了位置:
# 正确的做法
p.apply_async(scrawler_process, args=(crawler_start, i, crawler_sum, crawler_pace,))
# 错误的做法
p.apply_async(scrawler_process(crawler_start, i, crawler_sum, crawler_pace))
导致进程无法同时启动稍微耽搁了一会儿,没有别的问题。
这里要提一下,为什么用多进程而非多线程。引用一下廖雪峰大神的话吧————
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
我试着开4个进程爬4组数据,结果————
与B站反爬虫的初次交手
嗯哼,我被封IP了。一开始封个半分钟到三分钟不等,我干脆让爬虫歇息30秒再爬(所以源代码里还有sleep(30)
的代码),结果B站把我封了一个多小时,我差点以为这辈子再也没法用宿舍的网上B站了……
让爬虫休息没用,我开始推测B站的反爬虫策略。我试着不持续访问用户主页去别的页面转悠转悠,可没有明显效果;改请求头也成效不大,看来B站就是根据IP地址访问频率来反爬虫的。
终极的对策是用IP代理池和分布式爬取。可我就做一个学校的作业,不想搞得那么麻烦(其实是太菜没做过)。看来还是别爬得太狠,开一两个进程就行了。我暂时认输。
这里分享一篇分布式爬虫的文章:分布式B站爬虫任务系统
还有高素质的知乎:IP代理池相关讨论
《Python爬虫开发与项目实战》作者的博客:据说大家都能用的IP代理池
利用API?
自己搞不定,就上网搜呗。网上大量的爬虫都是一两年前的,当时B站反爬虫还没现在那么狠,甚至有对外公开的API
接口。可惜B站被爬怕了。如果有条件,去试着申请API
吧~我只能放弃。
效率优化
还是回到无头浏览器的老路子。我阅读了几篇相关的文章,这里罗列出来:
盘点selenium phantomJS使用的坑
我认为的重点/碰到的坑:
phantomJS
无头浏览器的配置(不加载图片、请求头、代理、超时返回等)phantomJS
的并发问题(多线程满满的BUG,还好我用的多进程)
Phantomjs性能优化
我认为的重点/碰到的坑:
- 关闭图片加载功能似乎有问题,会导致浏览器进程莫名罢工
- 全篇都是重点
【phantomjs系列】Selenium+Phantomjs爬过的那些坑
Selenium+PhantomJS的爬虫那些事儿
我认为的重点/碰到的坑:
- Phantomjs连续访问网页时的状态污染问题么……保险起见,我按文中所述,每次访问网页后加上
driver.get("about:blank")
- 这两篇文章说的是同一个项目的同一个问题,由该项目的不同技术人员写的
知乎-selenium 怎样设置请求头?
我认为的重点/碰到的坑:
- 全篇都是重点
- 注意
Chrome
也有无头版本的。鉴于phantomjs
已经无人维护,使用Chrome headless
才是正道
文件读写、字符串函数、字符集、随机函数、无效UID
爬取下来的原始数据不适合导入数据库处理。我用字符串函数好好休整了一番,其间艰辛自不必说,看源码吧。
爬取的数据总得找个地方存。应同学的要求,我选择.csv
格式存储。也不难写:
千辛万苦走到这一步,可以很愉快地爬数据了是不是?Naive!
字符集!字符集!字符集!Python
默认的编码格式与操作系统相同————中文系统应该是GBK
。而我的爬虫在爬到某个用户昵称里有阿拉伯语的奇葩时,很愉快地报错挂掉了…………
还好问题发现得早,这样改一改就行:
out = open(filename, "a", newline="", encoding="utf-8")
另一个问题就发现得晚了————爬取的用户生日集中在一月份————原因是我的爬虫每隔2500个uid
爬取一次数据,导致爬取的用户生日都撞车。这个问题发现时已经爬了五千组数据了,这个问题嘛,Emmmmmmm……于是要给urlNumber
加上随机函数。
另外,可能是B站回滚过数据库,有大片连续的uid
是无效的。所以我设置了连续失败三次后urlNumber
增加量为正常pace
的十倍,以此来跳过无效的uid
。
这里顺便附上在linux
系统下遇到乱码问题的解决方案。
第二篇文章中,这个命令很好使:
iconv -f gb2312 -t utf-8 test.txt> testutf8.tzt
命令格式一目了然,就不多做说明。
终于可以愉快地爬数据了~~~
结尾的一些闲话
我有一个学姐半开玩笑地说过一句话:
程序猿其实应该属于手工业,才不是什么高新技术产业。
确实,码代码是需要熟练的,和做手工活一样。这个爬虫从技术原理上讲没有很高的难度(也就比入门级高一点而已),真写起代码来问题丛生。想成为合格的程序猿?“无他,唯手熟耳。”
幸甚至哉,歌以咏志。