前言
在上篇,我们获得了豆瓣二百五的电影URL,然后存储在了一个文件里。接下来,我们要访问每一个电影URL,深入敌后,获取情报~
所有的代码都已存储在我的Github仓库:Douban_250当中~
设置爬取规则
对于每一个电影,我们选择爬取如下内容(虽然电影列表页就能爬得到= =):
1 | 标题(title)、年份(year)、时长(time)、 |
随意点击一个电影页面,用上篇所说的提取CSS选择器的方法,我们可以制作出每一种内容与内容提取规则的映射。如下所示:
1 | # sp就是上篇所说的soup解析器 |
设置完规则,大问题来了= =250个链接,一个个访问,应该会很慢吧!而且,豆瓣还有反爬虫机制,如果访问间隔太快,就会暂时封ip——这,这可怎么办呀!
人海战术,代理爬虫
一个个访问慢的话,一起访问,似乎会更快的吧= =
豆瓣封ip的话,如果有多个ip,效率也会增加的吧= =
效率调度
理想状态下,我们希望爬取每一个url的过程,都能够被当作是一个任务(task)。就像在M记一样,前台每收到一个客人的订单,都会把订单任务直接扔给厨师们,然后处理下一个客人的请求。那么,我们的厨师在哪里?
得益于python内置的asyncio
库,我们可以模拟厨师们的工作,调度每一个爬虫任务。
然而,即使有了asyncio
库,我们也只是通过其中的调度机制,重构了CPU指令而已,对于某些阻塞(block)任务进度的过程,并不能妥善解决。在爬取url内容的过程中,最阻碍我们任务进度的,当属请求——响应的阶段。对于调度器来讲,一个任务发送请求出去,调度器并不需要让它傻等响应回来,而是可以让这个任务歇一下,把执行权让(yield)给其它任务,直到响应回来后,再通知这个任务继续原来的工作,这样才够效率。上一章我们用的requests
库不支持这一种机制,没关系,我们可以利用第三方的aiohttp
库,完成这个需求。
获取代理池
HTTP请求支持我们通过代理发送数据,使得目标识别发送源为代理服务器。要短时间获取大量的代理服务器地址,很简单,随便找个比如西刺代理或者66代理,利用内置查询,或者HTML解析爬取一堆,搞个一两千个就好。
代理服务器分配
在我们的人海战术里,每一个爬虫任务开始前,都需要分配到一个代理服务器。所以问题来了——代理服务器,该怎么分配给每一个任务呢?我们希望,每一个任务都像被等待点名一样,如果有可用的代理服务器就挑几个任务去用,如果暂时能用的代理服务器都在用的话,就歇一会儿。
当然,如果没有代理服务器能用,就凉凉了= =
得益于asyncio
库提供条件变量(Condition Variable)的机制,可以满足我们的需求。需要获得资源的任务,就去等待(wait)资源分配器的通知,资源分配器发现资源可用,则去通知(notify)那些迫不及待的任务,叫他们赶紧获取资源去。具体如何操作?且让我徐徐道来~
首先,在爬虫任务开始之前,我们得把“获取代理池”一步拿到的代理服务器列表,放到咱们内存里。
1 | # 初始化条件变量 |
然后,开始添加我们的任务啦
1 | # 创建用于发送HTTP请求的客户端 |
对于每一个任务,我们都做成一个循环(loop),直到任务完成或者没有代理服务器可用,才退出不干。因此,爬虫任务的初始逻辑如下:
1 | # 加了async,表示这个函数上升为一个可被asyncio模块调度的任务 |
对于每一个任务来讲,该如何获取代理服务器呢?咱们的get_proxy
任务代码如下:
1 | async def get_proxy(): |
面对这群嗷嗷待哺的任务们,我们的代理服务器分配任务allocate_proxy
,就可以这样设计了
1 | async def allocate_proxy(max_tasks): |
爬虫主任务
有了代理分配这个强劲的后盾,我们的爬虫任务就可以顺利进行啦!
免费的代理好多都没法用的,需要在爬虫的过程中不断舍弃。废话不多说,直接上代码!
1 | async def crawl_movie_url(session, url, movie_num): |
试试看吧~
总结
豆瓣Top 250爬虫,其实更多的难点,在于如何组织、调度你的资源,更有效率地处理数据。爬虫的工具、软件,其实都已经烂大街
对于软件,集搜客、八爪鱼之类的就能完成需求
对于代理池,github上就有许多项目可以clone下来去获得
对于爬虫框架,其实可以踩踩scrapy
库的坑,这是一个非常成熟的爬虫框架
对于获取异步加载(不是当即就在response里,而是后面才加载到)的数据,可以使用PhantomJS一类的工具,或者利用Chrome开发者工具,采用抓包+模拟HTTP请求的方式,获取相应数据。
最后介绍一个大杀器——Selenium,作为一款浏览器测试驱动,selenium甚至可以模拟浏览器操作,百试不爽,谁用谁知道!