前言

在上篇,我们获得了豆瓣二百五的电影URL,然后存储在了一个文件里。接下来,我们要访问每一个电影URL,深入敌后,获取情报~

所有的代码都已存储在我的Github仓库:Douban_250当中~

设置爬取规则

对于每一个电影,我们选择爬取如下内容(虽然电影列表页就能爬得到= =):

标题(title)、年份(year)、时长(time)、
导演(director)、类型(genre)、评分(score)

随意点击一个电影页面,用上篇所说的提取CSS选择器的方法,我们可以制作出每一种内容与内容提取规则的映射。如下所示:

# sp就是上篇所说的soup解析器
# 每一种内容都可以在解析器中按特定的规则提取加工得到
content_func_map = {'title': lambda sp: sp.select_one('#content > h1 > span:nth-child(1)').get_text(),'year': lambda sp: sp.select_one('#content > h1 > span.year').get_text().replace('(', '').replace(')', ''),'time': lambda sp: sp.select_one('#info > span[property="v:runtime"]').get_text(),'director': lambda sp: sp.select_one('#info > span:nth-child(1) > span.attrs > a').get_text(),'genre': lambda sp: list(map(lambda item: item.get_text(), sp.select('#info > span[property="v:genre"]'))),'score': lambda sp: sp.select_one('#interest_sectl > div.rating_wrap.clearbox > div.rating_self.clearfix > strong').get_text()
}

设置完规则,大问题来了= =250个链接,一个个访问,应该会很慢吧!而且,豆瓣还有反爬虫机制,如果访问间隔太快,就会暂时封ip——这,这可怎么办呀!

人海战术,代理爬虫

一个个访问慢的话,一起访问,似乎会更快的吧= =
豆瓣封ip的话,如果有多个ip,效率也会增加的吧= =

效率调度

理想状态下,我们希望爬取每一个url的过程,都能够被当作是一个任务(task)。就像在M记一样,前台每收到一个客人的订单,都会把订单任务直接扔给厨师们,然后处理下一个客人的请求。那么,我们的厨师在哪里?

得益于python内置的asyncio库,我们可以模拟厨师们的工作,调度每一个爬虫任务。

然而,即使有了asyncio库,我们也只是通过其中的调度机制,重构了CPU指令而已,对于某些阻塞(block)任务进度的过程,并不能妥善解决。在爬取url内容的过程中,最阻碍我们任务进度的,当属请求——响应的阶段。对于调度器来讲,一个任务发送请求出去,调度器并不需要让它傻等响应回来,而是可以让这个任务歇一下,把执行权让(yield)给其它任务,直到响应回来后,再通知这个任务继续原来的工作,这样才够效率。上一章我们用的requests库不支持这一种机制,没关系,我们可以利用第三方的aiohttp库,完成这个需求。

获取代理池

HTTP请求支持我们通过代理发送数据,使得目标识别发送源为代理服务器。要短时间获取大量的代理服务器地址,很简单,随便找个比如西刺代理或者66代理,利用内置查询,或者HTML解析爬取一堆,搞个一两千个就好。

代理服务器分配

在我们的人海战术里,每一个爬虫任务开始前,都需要分配到一个代理服务器。所以问题来了——代理服务器,该怎么分配给每一个任务呢?我们希望,每一个任务都像被等待点名一样,如果有可用的代理服务器就挑几个任务去用,如果暂时能用的代理服务器都在用的话,就歇一会儿。

当然,如果没有代理服务器能用,就凉凉了= =

得益于asyncio库提供条件变量(Condition Variable)的机制,可以满足我们的需求。需要获得资源的任务,就去等待(wait)资源分配器的通知,资源分配器发现资源可用,则去通知(notify)那些迫不及待的任务,叫他们赶紧获取资源去。具体如何操作?且让我徐徐道来~

首先,在爬虫任务开始之前,我们得把“获取代理池”一步拿到的代理服务器列表,放到咱们内存里。

 # 初始化条件变量
cond = None
# 代理队列采用deque()数据结构,头尾都可以添加/删除,便于代理重复利用
proxies = deque()
# 存放当前正在使用的代理,如果同时被多个任务用的话,小心被豆瓣封= =
proxies_used = set() def get_proxies():"""获取存好的代理服务器列表"""contents = open('proxies.txt', encoding='utf-8').read().splitlines()for i in range(len(contents)):if not contents[i].startswith('http'):contents[i] = 'http://' + contents[i]proxies.extend(set(contents))

然后,开始添加我们的任务啦

# 创建用于发送HTTP请求的客户端
async with aiohttp.ClientSession() as session:# 电影数量num_urls = len(movie_urls)# 初始化条件变量Condition Variableglobal condcond = asyncio.Condition()# 添加任务啦~tasks = list()for i in range(num_urls):# crawl_movie_url就是我们爬虫任务的模版啦,具体见下面tasks.append(crawl_movie_url(session, movie_urls[i], i + 1))# 最后一个启动的任务是咱们的代理分配模块:allocate_proxytasks.append(allocate_proxy(len(tasks)))# 开始把这堆任务跑起来,走你~await asyncio.gather(*tasks)

对于每一个任务,我们都做成一个循环(loop),直到任务完成或者没有代理服务器可用,才退出不干。因此,爬虫任务的初始逻辑如下:

# 加了async,表示这个函数上升为一个可被asyncio模块调度的任务
async def crawl_movie_url(session, url, movie_num):while True:# 加了await,该任务就歇了,保存该任务状态,CPU执行权交给其它任务# 直到get_proxy有结果才被asyncio调度器唤醒proxy = await get_proxy()if not proxy:log(movie_num, 'TMD代理全部挂了,凉凉= =')return# 开始执行爬虫任务啦,具体后面说= =await set_proxy_in_use(proxy)pass

对于每一个任务来讲,该如何获取代理服务器呢?咱们的get_proxy任务代码如下:

async def get_proxy():# cond.acquire()跟cond.release(),可以实现对一段命令的加解锁(Lock)# 就如同上厕所一样,一个任务执行到加锁的一段,如果另一个任务也执行到这一段,就会暂停挂起,等待排队# proxies跟proxies_used是每个任务共享的全局变量(Global Variable),对于共享变量的操作,是要加锁的# 如果代理就剩一个,然后两个任务都跑到这一段,那就出事了= =await cond.acquire()proxy = ''try:# 等待代理分配器通知= =# 代理分配器会在有可用代理,或者代理全部凉凉的时候唤醒这些等待者await cond.wait()# 如果有可用代理,就挑一个,没有就直接返回空字符串# 可以看上面的crawl_movie_url任务,直接凉凉= =if len(proxies) > 0:# 从代理队列头部取出一个代理proxy = proxies.popleft()# 把这个代理加到正在使用的代理集合中proxies_used.add(proxy)finally:cond.release()return proxy

面对这群嗷嗷待哺的任务们,我们的代理服务器分配任务allocate_proxy,就可以这样设计了

async def allocate_proxy(max_tasks):# 代理分配任务,本身就相当于一个服务,因此这里采用循环的方式来设计while True:# 上锁,劳资要给别的任务分配代理了# 而且还得看代理队列呢,其他任务先别着急= =await cond.acquire()will_break = False  # 是否退出代理分配任务will_delay = False  # 下一个循环是否要延迟长一点try:if task_count == max_tasks:# task_count指当前完成的爬虫任务数# 如果到了最大值,说明全部任务都结束了# 这个时候代理分配任务也就完成任务了will_break = True# 看代理队列还有木有可用的  len_proxies = len(proxies)if len_proxies == 0:if len(proxies_used) == 0:# 代理队列没代理,连tm正在用的代理也是空的,岂不是凉凉= =# 把这个可怕的信息告诉给所有任务吧= =然后劳资也跑路= =cond.notify_all()will_break = Trueelse:# 代理都在用着呢,等多一会儿再给大家分配吧= =will_delay = Trueelse:# 代理队列有多少代理,最多通知多少个任务去拿代理去cond.notify(len_proxies)finally:cond.release()if will_break:breakelif will_delay:# 歇多几秒,再分配代理给其它嗷嗷待哺的任务# 相当于爬虫访问网页的时间间隔设置# 豆瓣白天2s左右晚上3~4s左右差不多await asyncio.sleep(get_proxy_delay_time())else:# 歇一丢丢时间,把自己排在任务调度后面# 不然while True死循环,别的任务就跑不了了await asyncio.sleep(proxy_search_period)

爬虫主任务

有了代理分配这个强劲的后盾,我们的爬虫任务就可以顺利进行啦!

免费的代理好多都没法用的,需要在爬虫的过程中不断舍弃。废话不多说,直接上代码!

async def crawl_movie_url(session, url, movie_num):while True:# 这段上面讲了= =proxy = await get_proxy()if not proxy:log(movie_num, 'TMD代理全部挂了,凉凉= =')return# result存储爬取的数据,log函数打印日志(得自定义实现= =)result = {'number': movie_num, 'url': url, 'proxy': proxy}log(movie_num, '代理%s正在访问%s...' % (proxy, url))success = True  # 是否爬取成功的标志try:# 用get获取数据,判断状态码,而后解析数据response = await session.get(url, proxy=proxy, headers=headers, timeout=proxy_connection_timeout)status_code = response.statusif status_code == 200:html = await response.text()soup = BeautifulSoup(html, html_parser)for k in content_func_map.keys():try:# 按最开始设的爬取规则抓数据,存到result里content = content_func_map[k](soup)result[k] = contentexcept Exception as e:# 如果爬不了,很有可能get到的网页变了!# 比如叫你登录啥啥的,这说明代理被豆瓣临时小黑屋了,果断放弃这个代理log(movie_num, '代理%s爬取%s信息失败!果断放弃掉!错误信息:%s\n' % (proxy, k, e))success = Falsebreakelse:# 如果状态码不对,很有可能是400啥的,说明也被豆瓣小黑屋了,果断放弃掉这个代理log(movie_num, '代理%s获取数据失败!果断放弃掉!状态码: %d!' % (proxy, status_code))success = Falseexcept Exception as e:# 代理链接或者代理发送请求都有问题,果断不要了 log(movie_num, '代理%s连接出错,果断放弃掉!!!错误信息:%s!' % (proxy, e))success = Falsefinally:if success:# 成功爬取数据,把爬取结果加上,把任务完成数加上global resultsglobal task_countresults.append(result)task_count = task_count + 1log(movie_num, '当前爬到信息的电影数: %d,爬到信息:%s' % (task_count, str(result)))# 这个代理还能用,给力!proxies_used里删掉它# 然后把它放到proxies的尾部(append)await recycle_proxy(proxy)breakelse:# 这个代理不给力,直接从proxies_used删掉吧~await remove_proxy(proxy)

试试看吧~

总结

豆瓣Top 250爬虫,其实更多的难点,在于如何组织、调度你的资源,更有效率地处理数据。爬虫的工具、软件,其实都已经烂大街

对于软件,集搜客、八爪鱼之类的就能完成需求

对于代理池,github上就有许多项目可以clone下来去获得

对于爬虫框架,其实可以踩踩scrapy库的坑,这是一个非常成熟的爬虫框架

对于获取异步加载(不是当即就在response里,而是后面才加载到)的数据,可以使用PhantomJS一类的工具,或者利用Chrome开发者工具,采用抓包+模拟HTTP请求的方式,获取相应数据。

最后介绍一个大杀器——Selenium,作为一款浏览器测试驱动,selenium甚至可以模拟浏览器操作,百试不爽,谁用谁知道!

【Easy Python】第四话:爬虫初探——玩转豆瓣二百五(下)相关推荐

  1. 爬虫初探:把豆瓣读书主页上书的URL、书名、作者、出版时间、出版社全部爬下来

    import requests import re #进入豆瓣读书主页,把网页源代码打出来 content = requests.get('https://book.douban.com/').tex ...

  2. Python爬虫初探(九)——爬虫之Beautifulsoup4实战(爬取豆瓣信息)

    前面两章咱们介绍了Beautifuisoup4模块的简单使用,今天就用它来爬取豆瓣信息.话不多说,咱们开始吧. 一.拿到url地址 二.获取豆瓣数据 三.保存文件 需求: 爬取标题.评分.详情页的地址 ...

  3. python中的网页解析器_python爬虫初探(三):HTML解析器

    爬虫初探系列一共4篇,耐心看完,我相信你就能基本了解爬虫是怎样工作的了,目录如下: 代码已上传至github,在python2.7下测试成功(请原谅我浓浓的乡村非主流代码风格)summerliehu/ ...

  4. python爬虫详细步骤-Python爬虫的两套解析方法和四种爬虫实现过程

    对于大多数朋友而言,爬虫绝对是学习 python 的最好的起手和入门方式.因为爬虫思维模式固定,编程模式也相对简单,一般在细节处理上积累一些经验都可以成功入门.本文想针对某一网页对 python 基础 ...

  5. [Python从零到壹] 四.网络爬虫之入门基础及正则表达式抓取博客案例

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  6. c#使用正则表达式获取TR中的多个TD_[Python从零到壹] 四.网络爬虫之入门基础及正则表达式抓取博客案例...

    首先祝大家中秋节和国庆节快乐,欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都 ...

  7. Python 爬虫瞎玩系列(1) —— Bilibili的前100个上古巨坟考古

    Python 爬虫瞎玩系列(1) -- Bilibili的前100个上古巨坟考古 现在是2017年5月25日13:29:56,嗯,神志正常. Python爬虫很难?不存在的. 只要学习我的课程< ...

  8. 【Python笔记】网络爬虫——常用框架介绍以及 Scrapy 框架使用

    网络爬虫开发常用框架 Scrapy 爬虫框架 Crawley 爬虫框架 PySpider 爬虫框架 Scrapy 爬虫框架的使用 搭建 Scrapy 爬虫框架 1. 安装 Twisted 模块 2. ...

  9. python最简单的爬虫代码,python小实例一简单爬虫

    python新手求助 关于爬虫的简单例子 #coding=utf-8from bs4 import BeautifulSoupwith open('', 'r') as file: fcontent ...

最新文章

  1. [Delphi] Webbroker ISAPI 示例说明
  2. Intel Realsense D435 pyrealsense set_option() rs.option 可配置参数翻译
  3. mysql 密码字段加密_phpmyadmin密码字段加密方法
  4. 最小二乘算法MATLAB代码实现
  5. 用户认证-什么是认证
  6. Ubuntu 16.04工具栏靠下设置
  7. Hadoop Balancer运行速度优化
  8. Android系统性能优化(44)---全面详细的内存优化指南
  9. 集合Collection总览
  10. 背靠 Google 的 Go 语言,就不会失败?
  11. Nginx+Tomcat+SSL 识别 https还是http
  12. .NET:持续进化的统一开发平台
  13. 查找Excel最后一个非空单元格的值,你会吗?(适用于数据加行时,一直引用最后一个非空单元格的值)
  14. IP被反垃圾邮件组织列入SBL,发送邮件被退回的解决方法
  15. 八:微服务调用组件Dubbo
  16. JavaSE方法(构造方法)与方法重载基础练习题
  17. Sentiment140数据集
  18. win10 GTX1060 安装CUDA+PyTorch GPU
  19. 【原创纯手打】如何用微信小程序写留言板(附代码)
  20. plt.plot() marker 一览表 (散点图)

热门文章

  1. 分析化学在计算机行业的应用,计算机在分析化学的应用,Computer Application in Analytical Chemistry,音标,读音,翻译,英文例句,英语词典...
  2. C语言实现推箱子(从1.0到3.1版本详解)
  3. 桌面计算机回收站打不开,电脑桌面上只有回收站怎么办 电脑只显示回收站的两种解决方法...
  4. 微软发布会前新平板Xbox Surface规格泄露
  5. 两数求最大公约数的三种方法的C语言实现
  6. OfficeClickToRun.exe禁了后word就打不开了
  7. 第一份工作(二)编写中
  8. Java 变量运算符
  9. 网络同步在游戏历史中的发展变化(四)—— 状态同步的发展历程与基本原理(下)...
  10. 腾讯miniStation评测 打造完美安卓游戏主机