CrawlSpider

前面,我们用了scrapy中的CrawlSpider爬取了糗事百科中大量段子数据。

但是,qiubai这个爬虫没有充分利用CrawlSpider的优点。其实,在qiubai这个爬虫里面我们只是把CrawlSpider当做普通的Spider用而已。

CrawlSpider继承自Spider,提供了Rule和LinkExtractor,使得爬虫框架能够自动按照规则提取Response中所有符合条件的链接,并且跟进这些链接继续爬取。从而,CrawlSpider非常适合爬取比较规则的网站。并且,我们只需写上合适的Rule和LinkExtractor,就能够避免自己在parse方法中写一大堆提取逻辑来提取链接和数据。

使用了CrawlSpider的特性(Rule和LinkExtractor)之后,我们只需要关注那些我们感兴趣的页面,编写parse方法从这些页面对应的Response上提取item数据即可。继续跟进和爬取页面的事情让CrawlSpider做就好。

让我们再来看一下Rule和LinkExtractor的作用:

首先,我们看一下CrawlSpider的主要属性和方法:

rules:列表,定义了从Response提取符合要求的链接的Rule对象。
parse_start_url:CrawlSpider默认的回调函数,我们在使用CrawlSpider时,不应该覆盖parse方法,而应该覆盖这个方法。因为CrawlSpider使用了parse函数来处理自己的逻辑,所以我们不能覆盖parse方法。

其中,Rule有以下几个参数:

link_extractor:LinkExtractor对象,用于定义需要提取的链接。Link Extractors是链接提取器,用来从返回网页的Response中提取符合要求的链接。
callback:回调函数,当link_extractor提取到符合条件的链接时,就会把该链接继续处理获得相应的Response,然后把该Response传递给这个函数处理。由于CrawlSpider内部使用了parse方法,所以我们自己设置的该回调函数一般不能为parse方法。callback不为空时,follow为false,表示该提取的链接对应的Response应该传递给callback设置的函数处理,而不应该继续用于跟进。
follow:布尔值,指定了根据link_extractor规则从response提取的链接是否需要跟进,跟进的意思就是指让CrawlSpider对这个提取的链接进行处理,继续拿到该链接的Response,然后继续使用所有的rule去匹配和提取这个Response,然后再不断的做这些步骤。callback为空时,follow默认为true,否则就是false。
process_links:函数或者函数名,用于过滤link_extractor提取到的链接。
process_request:函数或者函数名,用于过滤Rule中提取到的request。

其中,LinkExtractor对象主要有以下几个参数:

allow:字符串或元组,满足括号中所有的正则表达式的那些值会被提取,如果为空,则全部匹配。
deny:字符串或元组,满足括号中所有的正则表达式的那些值一定不会被提取。优先于allow参数。
allow_domains:字符串或元组,会被提取的链接的域名列表。
deny_domains:字符串或元组,一定不会被提取链接的域名列表。
restrict_xpaths:字符串或元组,xpath表达式列表,使用xpath语法和allow参数一起提取链接。
restrict_css:字符串或元素,css表达式列表,使用css语法和allow参数一起提取链接。

分析网页结构

进169ee网我们发现,网站总的来说有三种类型的链接,如图:

编写Rule和LinkExtractor处理上面类型的链接

所以我们在rules变量可以定义不同的rule分别对这三种链接进行提取和跟进。

  1. 对于相册列表页链接,因为页面中全是相册列表,所以我们只需要跟进该链接即可;
  2. 对于相册链接,由于里面都是图片,也就是说,页面里面包含我们感兴趣的数据,所以这样的页面,我们应该设置callback函数对Response进行处理,提取出我们需要的数据。而不仅仅是简单的跟进该链接。
  3. 对于相册翻页链接,由于翻页之后,页面其实跟相册链接页面一样,所以,我们需要把该类型链接的处理设置成跟2中一样。

最终,我们设置的rule列表如下:

Rule(SgmlLinkExtractor(allow=(r'^http://www.169ee.com/[a-z]+$',))),
Rule(SgmlLinkExtractor(allow=(r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/[0-9]*?\.html$', r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/\d*?_\d*?\.html$')), callback='parse_album'))

更详细的可以参见github源码!

编写parse_album函数,提取相册中的图片

设置好了rules之后,CrawlSpider会自动跟进链接了,所以我们集中精力编写提取图片的代码。

我们进入相册页面查看网页结构,发现,xpath表达式为//div[@id="content"]/div[@class="big-pic"]/div[@class="big_img"]的div标签中的每一个p标签里面都包含了一张图片。如,

定义Item

所以,提取这里面的所有图片链接,然后提取网页的title作为相册名字,从相册链接中提取年月日,和相册id,再从图片的链接中提取出图片的id。然后,我们定义的item如下:

class OnesixnineItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()_id = scrapy.Field()  # 该图片在该相册中的idtitle = scrapy.Field()  # 该相册的名字year = scrapy.Field()  # 该图片上传的年份folder_num = scrapy.Field()  # 该图片是该年份的第几个相册month_day = scrapy.Field()  # 该相册是该年份的几月几日创建的url = scrapy.Field()  # 该图片的urlalbum_id = scrapy.Field()album_url = scrapy.Field()

提取图片

定义好item之后,我们继续提取图片信息。编写提取函数如下:

class OnesixnineSpider(CrawlSpider):name = "onesixnine"allowed_domains = ['www.169ee.com', '724.169pp.net']start_urls = ['http://www.169ee.com']year_monthday_albumid_pattern = re.compile(r'http://www\.169ee\.com/[a-z]+/(\d*?)/(\d*?)/(\d*?)\.html')image_year_foldernum_imageid_pattern = re.compile(r'http://724\.169pp\.net/169mm/(\d*?)/(\d*?)/(\d*?)\.[a-z]*?')rules = (Rule(LinkExtractor(allow=(r'^http://www.169ee.com/[a-z]+$',))),Rule(LinkExtractor(allow=(r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/[0-9]*?\.html$',r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/\d*?_\d*?\.html$')),callback='parse_album'))def parse_album(self, response):img_srcs = response.xpath('//div[@id="content"]/div[@class="big-pic"]/div[@class="big_img"]/p/img/@src').extract()if not img_srcs:returnpage_r_index = response.url.rfind('_')slash_r_index = response.url.rfind('/')if page_r_index > slash_r_index:# 当前页面链接中存在分页的部分,去掉当前链接中的分页部分album_url_prefix, album_url_suffix = os.path.split(response.url)album_url = response.urljoin(re.compile(r'_\d+').sub('', album_url_suffix))else:album_url = response.url# 从当前页面链接中解析出该相册的年、月日、相册idyear_monthday_albumid_match = self.year_monthday_albumid_pattern.search(album_url)if not year_monthday_albumid_match:returnyear = year_monthday_albumid_match.group(1)month_day = year_monthday_albumid_match.group(2)album_id = year_monthday_albumid_match.group(3)for img_src in img_srcs:item = OnesixnineItem()item['title'] = response.xpath('/html/head/title/text()').extract_first()item['year'] = yearimage_year_foldernum_imageid_match = self.image_year_foldernum_imageid_pattern.search(img_src)if not image_year_foldernum_imageid_match:continueitem['folder_num'] = image_year_foldernum_imageid_match.group(2)image_local_id = image_year_foldernum_imageid_match.group(3)item['_id'] = year + month_day + album_id + item['folder_num'] + image_local_iditem['month_day'] = month_dayitem['album_id'] = album_iditem['album_url'] = album_urlitem['url'] = img_srcyield item

编写pipeline,保存图片

提取了item之后,scrapy会把Spider中产生的item转移给pipeline处理。所以我们可以在pipeline中保存图片。同时,我们可以保存图片信息到MongoDB中,待日后查看。

编写的pipeline如下:

class OnesixninePipeline(object):logger = logging.getLogger('OnesixninePipeline')connection = pymongo.MongoClient('localhost', 27017)def __init__(self):self.logger.info('pipeline init')self.db = self.connection.scrapy  # 切换到scrapy数据库self.collection = self.db.onesixnine  # 获取到onesixnine集合def process_item(self, item, spider):self.save_image(item)return itemdef save_image(self, item):if not item:returnif os.path.exists(os.path.dirname(settings.DEFAULT_OUTPUT_FOLDER)):save_folder = settings.DEFAULT_OUTPUT_FOLDERelse:save_folder = settings.CANDIDATE_DEFAULT_OUTPUT_FOLDERif not os.path.exists(save_folder):os.mkdir(save_folder)# 获取图片扩展名ext = os.path.splitext(item['url'])[1]# 得到图片保存的名字image_save_path = os.path.join(save_folder,item['year'] + '_' + item['month_day'] + '_' + item['folder_num'] + '_' + item['album_id'] + '_' + item['_id'] + ext)urllib.request.urlretrieve(item['url'], image_save_path)if not self.connection:return itemself.collection.save(item)def __del__(self):self.logger.info('pipeline exit!')

翻页抓取

我们还漏了一步。在相册链接页面,我们抓取了该页面所有的图片信息,但是一个相册有很多页,我们需要翻页抓取才不会漏掉相册中的图片。所以,我们通过xpath表达式,提取出下一页的链接://div[@id="content"]/div[@class="big-pic"]/div[@class="dede_pages"]/ul/li[last()]/a/@href,最终,翻页的处理为:

next_page_in_album = response.xpath('//div[@id="content"]/div[@class="bigpic"]/div[@class="dede_pages"]/ul/li[last()]/a/@href').extract_first()
if next_page_in_album and next_page_in_album != '#':next_page_url = response.urljoin(next_page_in_album)yield Request(next_page_url, callback=self.parse_album)

运行,查看结果

使用scrapy crawl onesixnine运行,可以自行查看运行结果

源码

源码在github上

喜欢的可以star,欢迎提issue或者pull request。

喜欢的可以关注微信公众号:

参考

  1. 我自己的头条号:爬虫进阶:CrawlSpider爬取169ee全站美女图片

爬虫进阶:CrawlSpider爬取169ee全站美女图片相关推荐

  1. Python爬虫-带你爬取高清美女图片

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于腾讯云 作者:明天依旧可好 ( 想要学习Python?Python学 ...

  2. Python爬虫递归调用爬取动漫美女图片

    效果 代码,码云地址 import datetime import os import randomfrom lxml import etreeurl_src='https://www.nvshens ...

  3. python 爬视频下载_Python爬虫进阶之爬取某视频并下载的实现

    这篇文章我们来讲一下在网站建设中,Python爬虫进阶之爬取某视频并下载的实现.本文对大家进行网站开发设计工作或者学习都有一定帮助,下面让我们进入正文. 这几天在家闲得无聊,意外的挖掘到了一个资源网站 ...

  4. 爬虫爬取二次元网站美女图片

    爬虫爬取二次元网站美女图片 前言 xpath解析 需求分析 代码编写 总代码 前言 本次需要爬取二次元网站cos板块下的图片 需要用到request模块与lxml模块,所以请提前安装好这两个模块,打开 ...

  5. Python爬虫之爬取绝对领域美女图片

    Python爬虫之爬取绝对领域美女图片 第一步: 导入模块: import requests from lxml import etree 第二步:定义函数: def get_url(start_ur ...

  6. 2021-7-30 Python-爬虫练手:爬取高质量美女图片

    爬取高质量美女图片 目标网站 思路分析 封面页 用bs4确定封面页图片链接 拿到图片链接和名称 保存封面 子页面(套图) 确定子页面位置 获取子页面里图片位置和图片名称 保存组图 完整代码 运行结果 ...

  7. python爬取街拍美女图片

    python爬取街拍美女图片 完整代码: import requests from urllib.parse import urlencode import os from hashlib impor ...

  8. Java爬虫之利用Jsoup+HttpClient爬取类叔叔不约匿名聊天网站的图片,未果——后爬取某网站美女图片案例

    博主最近学了一点爬虫的知识,闲着无聊,秉承学以致用的理念,于是突然想到何不挑战一下,爬取一些叔叔不约网站的图片,来巩固一下所学知识(#滑稽).说干就干,打开eclipse或idea,创建maven工程 ...

  9. python3.6爬虫案例:爬取朝秀帮图片

    一.写在前面 之前写的两篇博客: python3.6爬虫案例:爬取百度歌单点击打开链接: python3.6爬虫案例:爬取顶点小说(爱看小说同学的福利)点击打开链接 第一个案例写了如何在百度音乐歌单中 ...

  10. python爬虫:新手爬取NASA每日精选图片

    作为一位科幻爱好者,初学python后,便想要使用python爬虫爬些漂亮的宇宙图片,于是乎就把目标瞄准了NASA的每日精选图片. 不过话说回来,毕竟NASA这样的官方组织我们还是要看看是否允许爬虫访 ...

最新文章

  1. mysql 求数据的长度_mysql 如何求数据的长度
  2. Circuit Breaker(电路熔断器)模式原理
  3. Flashback Query
  4. 浅析多个单列索引和联合索引的区别
  5. linux 关闭web服务器端口,linux(solaris)下如何关掉端口及服务
  6. java 多项式拟合最多的项数_Matlab概率统计与曲线拟合
  7. java 7 jboss_JBoss7 入门指南
  8. 家的N次方 经典台词
  9. Dynamips ADSL实验之一pppoeoa(工大瑞普修正版)
  10. php中字符串的截取函数,PHP字符串截取和截取函数的介绍
  11. THULAC 词性表
  12. php独孤九剑,(独孤九剑)--PHP 视频学习 -- 文件系统
  13. TC Games教你如何在电脑上玩手游梦幻西游,无需模拟器
  14. java blackjack card game_Java BlackJack Game Ace值
  15. 手机邮箱怎么弄_安卓手机邮箱设置教程 教您如何使用手机接收邮件
  16. [WPF系列] 高级 调试
  17. 贵大和云大计算机,云南大学和贵州大学哪个实力更强?有哪些优势学科?
  18. 一文让你彻底理解关于消息队列的使用
  19. FPGA视频拼接器 信号裁剪功能
  20. iOS 全景播放器最简单的解决方案

热门文章

  1. 盛天海电商:拼多多的定价策略有哪些?
  2. 小组取什么名字好_如何给公司取一个好名字?让你的公司脱颖而出
  3. CodeWarrior使用教程第一部分: 认识 CodeWarrior
  4. python 小说 云_Python 爬虫之网络小说下载
  5. 使用Android studio开发Android App
  6. 现在世界上到底有多少飞机?
  7. 【清华大学】操作系统 陈渝 Part4 ——物理内存管理 之 非连续内存分配
  8. 「计算机网络」五层因特网协议栈的简要介绍和分组名称
  9. http://atom8023.blog.51cto.com/2651558/1333582
  10. matlab rms数据滤波,与RMS相关的5个信息,如何通过RMS结果滤波来提高精度