Scrapy Learning笔记(四)- Scrapy双向爬取
摘要:介绍了使用Scrapy进行双向爬取(对付分类信息网站)的方法。
所谓的双向爬取是指以下这种情况,我要对某个生活分类信息的网站进行数据爬取,譬如要爬取租房信息栏目,我在该栏目的索引页看到如下页面,此时我要爬取该索引页中的每个条目的详细信息(纵向爬取),然后在分页器里跳转到下一页(横向爬取),再爬取第二页中的每个条目的详细信息,如此循环,直至最后一个条目。

这样来定义双向爬取:
水平方向 – 从一个索引页到另一个索引页
纯直方向 – 从一个索引页到条目详情页
在本节中,
提取索引页到下一个索引页的xpath为:'//*[contains(@class,"next")]//@href'
提取索引页到条目详情页的xpath为:'//*[@itemprop="url"]/@href'
manual.py文件的源代码地址:
把之前的basic.py文件复制为manual.py文件,并做以下修改:
导入Request:from scrapy.http import Request
修改spider的名字为manual
更改starturls为'http://web:9312/properties/index00000.html'
将原料的parse函数改名为parse_item,并新建一个parse函数,代码如下:
#本函数用于提取索引页中每个条目详情页的超链接,以及下一个索引页的超链接
def parse(self, response):
# Get the next index URLs and yield Requests
next_selector = response.xpath('//*[contains(@class,"next")]//@href')
for url in next_selector.extract():
yield Request(urlparse.urljoin(response.url, url))#Request()函数没有赋值给callback,就会默认回调函数就是parse函数,所以这个语句等价于
yield Request(urlparse.urljoin(response.url, url), callback=parse) # Get item URLs and yield Requests
item_selector = response.xpath('//*[@itemprop="url"]/@href')
for url in item_selector.extract():
yield Request(urlparse.urljoin(response.url, url),
callback=self.parse_item)
如果直接运行manual,就会爬取全部的页面,而现在只是测试阶段,可以告诉spider在爬取一个特定数量的item之后就停止,通过参数:-s CLOSESPIDER_ITEMCOUNT=10
运行命令:$ scrapy crawl manual -s CLOSESPIDER_ITEMCOUNT=10
它的输出如下:

spider的运行流程是这样的:首先对start_url中的url发起一个request,然后下载器返回一个response(该response包含了网页的源代码和其他信息),接着spider自动将response作为parse函数的参数并调用。
parse函数的运行流程是这样的:
1. 首先从该response中提取class属性中包含有next字符的标签(就是分页器里的“下一页”)的超链接,在第一次运行时是:'index_00001.html'。
2. 在第一个for循环里首先构建一个完整的url地址(’http://web:9312/scrapybook/properties/index_00001.html'),把该url作为参数构建一个Request对象,并把该对象放入到一个队列中(此时该对象是队列的第一个元素)。
3. 继续在该respone中提取属性itemprop等于url字符的标签(每一个条目对应的详情页)的超链接(譬如:'property_000000.html')。
4. 在第二个for循环里对提取到的url逐个构建完整的url地址(譬如:’http://web:9312/scrapybook/properties/ property_000000.html’),并使用该url作为参数构建一个Request对象,按顺序将对象放入到之前的队列中。
5. 此时的队列是这样的
|
Request(http://…index_00001.html) |
Request(http://…property_000000.html) |
… |
Request(http://…property_000029.html) |
6. 当把最后一个条目详情页的超链接(property_000029.html)放入队列后,调度器就开始处理这个队列,由后到前把队列的最后一个元素提取出来放入下载器中下载并把response传入到回调函数(parse_item)中处理,直至到了第一个元素(index_00001.html),因为没有指定回调函数,默认的回调函数是parse函数本身,此时就进入了步骤1,这次提取到的超链接是:'index_00002.html',然后就这样循环下去。
这个parse函数的执行过程类似于这样:
next_requests = []
for url in...
next_requests.append(Request(...))
for url in...
next_requests.append(Request(...))
return next_requests
可以看到使用后进先出队列的最大好处是在处理一个索引页时马上就开始处理该索引页里的条目列表,而不用维持一个超长的队列,这样可以节省内存,有没有觉得上面的parse函数写得有些让人难以理解呢,其实可以换一种更加简单的方式,对付这种双向爬取的情况,可以使用crawl的模板。
首先在命令行里按照crawl的模板生成一个名为easy的spider
$ scrapy genspider -t crawl easy web
打开该文件
...
class EasySpider(CrawlSpider):
name = 'easy'
allowed_domains = ['web']
start_urls = ['http://www.web/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
...
可以看到自动生成了上面的那些代码,注意这个spider是继承了CrawlSpider类,而CrawlSpider类已经默认提供了parse函数的实现,所以我们并不需要再写parse函数,只需要配置rules变量即可
rules = (
Rule(LinkExtractor(restrict_xpaths='//*[contains(@class,"next")]')),
Rule(LinkExtractor(restrict_xpaths='//*[@itemprop="url"]'),
callback='parse_item')
)
运行命令:$ scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=90
这个方法有以下不同之处:
这两个xpath与之前使用的不同之处在于没有了a和href这两个约束字符,因为LinkExtrator是专门用来提取超链接的,所以会自动地提取标签中的a和href的值,当然可以通过修改LinkExtrator函数里的参数tags和attrs来提取其他标签或属性里的超链接。
还要注意的是这里的callback的值是字符串,而不是函数的引用。
Rule()函数里设置了callback的值,spider就默认不会跟踪目标页里的其他超链接(意思是说,不会对这个已经爬取过的网页使用xpaths来提取信息,爬虫到这个页面就终止了)。如果设置了callback的值,也可以通过设置参数follow的值为True来进行跟踪,也可以在callback指定的函数里return/yield这些超链接。
在上面的纵向爬取过程中,在索引页的每一个条目的详情页都分别发送了一个请求,如果你对爬取效率要求很高的话,那就得换一个思路了。很多时候在索引页中对每一个条目都做了简介,虽然信息并没有详情页那么全,但如果你追求很高的爬取效率,那么就不能逐个访问条目的详情页,而是直接从索引页中获取条目的信息。所以,你要平衡好效率与信息质量之间的矛盾。
再次观察索引页,其实可以发现每个条目的节点都使用了itemptype=”http://schema.org/Product”来标记,于是直接从这些节点中获取条目信息。

使用scrapy shell工具来再次分析索引页:
scrapy shell http://web:9312/properties/index_00000.html

上图中的每一个Selector都指向了一个条目,这些Selector也是可以用xpath来解析的,现在就要循环解析着30个Selector,从中提取条目的各种信息
fast.py源文件地址:
将manual.py文件复制并重命名为fast.py,做以下修改:
将spider名称修改为fast
修改parse函数,如下
def parse(self, response):
# Get the next index URLs and yield Requests,这部分并没改变
next_sel = response.xpath('//*[contains(@class,"next")]//@href')
for url in next_sel.extract():
yield Request(urlparse.urljoin(response.url, url)) # Iterate through products and create PropertiesItems,改变的是这里
selectors = response.xpath(
'//*[@itemtype="http://schema.org/Product"]')
for selector in selectors: # 对selector进行循环
yield self.parse_item(selector, response)
- 修改parse_item函数如下
#有几点变化:
#1、xpath表达式里全部用了一个点号开头,因为这是在selector里面提取信息,所以这个一个相对路径的xpath表达式,这个点号代表了selector
#2、ItemLoader函数里用了selector变量,而不是response变量 def parse_item(self, selector, response):
# Create the loader using the selector
l = ItemLoader(item=PropertiesItem(), selector=selector) # Load fields using XPath expressions
l.add_xpath('title', './/*[@itemprop="name"][1]/text()',
MapCompose(unicode.strip, unicode.title))
l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
MapCompose(lambda i: i.replace(',', ''), float),
re='[,.0-9]+')
l.add_xpath('description',
'.//*[@itemprop="description"][1]/text()',
MapCompose(unicode.strip), Join())
l.add_xpath('address',
'.//*[@itemtype="http://schema.org/Place"]'
'[1]/*/text()',
MapCompose(unicode.strip))
make_url = lambda i: urlparse.urljoin(response.url, i)
l.add_xpath('image_urls', './/*[@itemprop="image"][1]/@src',
MapCompose(make_url)) # Housekeeping fields
l.add_xpath('url', './/*[@itemprop="url"][1]/@href',
MapCompose(make_url))
l.add_value('project', self.settings.get('BOT_NAME'))
l.add_value('spider', self.name)
l.add_value('server', socket.gethostname())
l.add_value('date', datetime.datetime.now()) return l.load_item()
运行spider:scrapy crawl fast –s CLOSESPIDER_PAGECOUNT=10

可以看到,爬取了300个item,却只发送了10个request(因为在命令里指定了只爬取10个页面),效率提高了很多
Scrapy Learning笔记(四)- Scrapy双向爬取的更多相关文章
- Scrapy:学习笔记(2)——Scrapy项目
Scrapy:学习笔记(2)——Scrapy项目 1.创建项目 创建一个Scrapy项目,并将其命名为“demo” scrapy startproject demo cd demo 稍等片刻后,Scr ...
- 爬虫(十七):Scrapy框架(四) 对接selenium爬取京东商品数据
1. Scrapy对接Selenium Scrapy抓取页面的方式和requests库类似,都是直接模拟HTTP请求,而Scrapy也不能抓取JavaScript动态谊染的页面.在前面的博客中抓取Ja ...
- 初识scrapy,美空网图片爬取实战
这俩天研究了下scrapy爬虫框架,遂准备写个爬虫练练手.平时做的较多的事情是浏览图片,对,没错,就是那种艺术照,我骄傲的认为,多看美照一定能提高审美,并且成为一个优雅的程序员.O(∩_∩ ...
- scrapy进阶(CrawlSpider爬虫__爬取整站小说)
# -*- coding: utf-8 -*- import scrapy,re from scrapy.linkextractors import LinkExtractor from scrapy ...
- (4)分布式下的爬虫Scrapy应该如何做-规则自动爬取及命令行下传参
本次探讨的主题是规则爬取的实现及命令行下的自定义参数的传递,规则下的爬虫在我看来才是真正意义上的爬虫. 我们选从逻辑上来看,这种爬虫是如何工作的: 我们给定一个起点的url link ,进入页面之后提 ...
- scrapy过滤重复数据和增量爬取
原文链接 前言 这篇笔记基于上上篇笔记的---<scrapy电影天堂实战(二)创建爬虫项目>,而这篇又涉及redis,所以又先熟悉了下redis,记录了下<redis基础笔记> ...
- 【图文详解】scrapy安装与真的快速上手——爬取豆瓣9分榜单
写在开头 现在scrapy的安装教程都明显过时了,随便一搜都是要你安装一大堆的依赖,什么装python(如果别人连python都没装,为什么要学scrapy….)wisted, zope interf ...
- Scrapy案例02-腾讯招聘信息爬取
目录 1. 目标 2. 网站结构分析 3. 编写爬虫程序 3.1. 配置需要爬取的目标变量 3.2. 写爬虫文件scrapy 3.3. 编写yield需要的管道文件 3.4. setting中配置请求 ...
- 爬虫系列4:scrapy技术进阶之多页面爬取
多页面爬取有两种形式. 1)从某一个或者多个主页中获取多个子页面的url列表,parse()函数依次爬取列表中的各个子页面. 2)从递归爬取,这个相对简单.在scrapy中只要定义好初始页面以及爬虫规 ...
随机推荐
- html 去掉input 获取焦点时的边框
html中,当input标签获取焦点的时候(例如,当光标放在input框中准备输入值时), input标签外围会出现边框,有的时候我们需要去掉这个边框,可以使用css的outline:none;属性将 ...
- Spark ThriftServer使用的大坑
当用beeline连接default后,通过use xxx切换到其他数据库,再退出, 再次使用beeline -u jdbc:hive2://hadoop000:10000/default -n sp ...
- 【转】WEB测试到移动测试的转换
移动互联网的发展毋庸置疑是必然的趋势,我们曾经传统WEB互联网测试的同学,也必然走上移动测试的道路,移动测试与pc测试到底需要怎样的思维转变才能更快的进入移动节奏呢?对比下WEB与移动的测试不同点: ...
- Python计算程序运行时间
方法1 import datetime starttime = datetime.datetime.now() #long running endtime = datetime.datetime.no ...
- Java虚拟机内存模型及垃圾回收监控调优
Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存 ...
- html中button的type属性
接触web开发不久,今天遇到了一个问题,点击button按钮,浏览器没有反应,尝试了自己可以想到的所有办法,还是无果.只得请教他人,才发现是button的type属性搞得怪,原来: ...
- 学习练习 Java冒泡排序 二分查找法
冒泡排序: // 冒泡排序 /* System.out.println("请输入要排序的个数:"); Scanner v = new Scanner(System.in); int ...
- 学习总结 html 表格标签的使用
表格: <table></table>表格 width:宽度.可以用像素或百分比表示. 常用960像素. border:边框,常用值为0. cellpadding:内容跟边框的 ...
- MacOSX和Windows 8的完美融合
MacOSX和Windows8的完美融合 一般情况下我们要在MACOS系统下运行Windows软件怎么办呢?一种方法我们可以装CrossOver这款软件,然后在configuration->in ...
- 按照 where id in ()排序
select * from ibs6_terminal_adv_inf where id in (16,14,15) order by find_in_set(id,'16,14,15')