不使用爬虫框架编写爬虫的注意事项

注:特指Python爬虫

异步IO

通常不用框架编写爬虫的时候,我们经常会去使用requests库作为网络库(这是一个非常厉害的第三方库)。当然也不排除有人会去用built-in的urllib库作为网络库。但是如果需要爬取的页面数量特别多的时候,直接使用这些同步的(Synchronous)网络库就会严重拖慢整个爬虫的速度。因此,如果可能的话,我们应当尽量使用异步的网络库,例如用future包装requests库的requests-futures库(Python 3)、基于原生asyncio库的aiohttp库(Python 3.5+)或者使用Twisted框架(Python 2)。使用这些异步网络库将会极大地提高爬虫速度。另外,并不推荐在多线程条件下使用同步库。GIL(CPython特色)的存在使得所谓的多线程看起来是多线程(这些线程确确实实是系统级的线程),但实际上任一时刻上,有且仅有一个线程在运行。尽管使用多线程确实使爬虫速度得到极大的提升,但有两个缺点:频繁的上下文切换无意义地耗费了CPU时间以及上下文切换不可控。异步虽然也只用一个线程,但是上下文切换是受程序本身控制地,因此效率更高。经验之谈:requests-futures的手感跟requests一样;aiohttp的效率比requests-futures高,其实也没有高太多,但用起来手感就和requests差很多。

控制并发

使用同步网络库不常见,但初次使用异步网络库经常会发生的问题:并发数量过多。通常编写异步网络IO的时候,会把所有要爬的地址直接往事件循环里扔。对本地运行的程序而言,几千、几万个请求一起发起,一点问题都没有。但对于网页服务器而言。短时间内来自同一IP密集的请求足以触发保护机制,让你做个人机检验什么的。当然如果目标服务器并没有做任何防护(这年头不做防洪的服务器也是少),那很有可能出现的结果是:程序一直报告HTTP请求超时,而你却一头雾水,甚至还想加一个超时重试的wrapper(人生经验)。用aiohttp的话可以直接用aiohttp.Semaphore设置最大并发连接数,并在网络请求代码上用with semaphore语句包上就好;用requests-futures就更简单粗暴了——设置ThreadExecutorPool的maxworker参数就好了。

内存控制

一般同步编程模型不会碰到,只在一个函数要执行多个异步网络IO时,这个问题尤为突出。一定要在下一次异步网络IO执行前,将本函数不需要的数据删除掉。正所谓“多么小的问题乘以13亿,都会变得很大”

AJAX的处理

碰到含有AJAX的页面,微笑就好了。因为处理方法很单一,只需要打开浏览器的开发者工具,仔细寻找对应的ajax请求,再构造一个请求就行了。而且通常ajax请求的报文内容是格式化的,要么是Json要么是xml(毕竟它名字就叫Asynchronous JavaScript and XML)。

JavaScript的处理

通常资源类的网站特别喜欢用JavaScript动态加载内容,因为这样就能把页面静态化,静态化了之后就能轻松上CDN,降低综合网站成本。然而对爬虫来说,带来的问题就是:Python并不能直接eval页面上的JavaScript。虽然有很多第三方库可以用来直接eval页面上的JavaScript(比如PhantomJS库),甚至实在不行,我们还可以直接用浏览器环境呀(比如Selenium库)。但是这些都不推荐去做,我们应该做的应该是去解析页面上的JavaScript到底干了什么。所有JavaScript能做的,Python都能做(多数情况下Python实现起来还会更简单)。因此对于JavaScript的处理原则就是:读懂它,然后把你需要的部分用Python实现它;不会读JavaScript,赶紧上Selenuim保平安(虽然效率奇差无比)。

留下评论