摘要:介绍了使用Scrapy进行双向爬取(对付分类信息网站)的方法。

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

这样来定义双向爬取:

  • 水平方向 – 从一个索引页到另一个索引页

  • 纯直方向 – 从一个索引页到条目详情页

在本节中,

提取索引页到下一个索引页的xpath为:'//*[contains(@class,"next")]//@href'

提取索引页到条目详情页的xpath为:'//*[@itemprop="url"]/@href'

manual.py文件的源代码地址:

https://github.com/Kylinlin/scrapybook/blob/master/ch03%2Fproperties%2Fproperties%2Fspiders%2Fmanual.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源文件地址:

https://github.com/Kylinlin/scrapybook/blob/master/ch05%2Fproperties%2Fproperties%2Fspiders%2Ffast.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双向爬取的更多相关文章

  1. Scrapy:学习笔记(2)——Scrapy项目

    Scrapy:学习笔记(2)——Scrapy项目 1.创建项目 创建一个Scrapy项目,并将其命名为“demo” scrapy startproject demo cd demo 稍等片刻后,Scr ...

  2. 爬虫(十七):Scrapy框架(四) 对接selenium爬取京东商品数据

    1. Scrapy对接Selenium Scrapy抓取页面的方式和requests库类似,都是直接模拟HTTP请求,而Scrapy也不能抓取JavaScript动态谊染的页面.在前面的博客中抓取Ja ...

  3. 初识scrapy,美空网图片爬取实战

          这俩天研究了下scrapy爬虫框架,遂准备写个爬虫练练手.平时做的较多的事情是浏览图片,对,没错,就是那种艺术照,我骄傲的认为,多看美照一定能提高审美,并且成为一个优雅的程序员.O(∩_∩ ...

  4. scrapy进阶(CrawlSpider爬虫__爬取整站小说)

    # -*- coding: utf-8 -*- import scrapy,re from scrapy.linkextractors import LinkExtractor from scrapy ...

  5. (4)分布式下的爬虫Scrapy应该如何做-规则自动爬取及命令行下传参

    本次探讨的主题是规则爬取的实现及命令行下的自定义参数的传递,规则下的爬虫在我看来才是真正意义上的爬虫. 我们选从逻辑上来看,这种爬虫是如何工作的: 我们给定一个起点的url link ,进入页面之后提 ...

  6. scrapy过滤重复数据和增量爬取

    原文链接 前言 这篇笔记基于上上篇笔记的---<scrapy电影天堂实战(二)创建爬虫项目>,而这篇又涉及redis,所以又先熟悉了下redis,记录了下<redis基础笔记> ...

  7. 【图文详解】scrapy安装与真的快速上手——爬取豆瓣9分榜单

    写在开头 现在scrapy的安装教程都明显过时了,随便一搜都是要你安装一大堆的依赖,什么装python(如果别人连python都没装,为什么要学scrapy….)wisted, zope interf ...

  8. Scrapy案例02-腾讯招聘信息爬取

    目录 1. 目标 2. 网站结构分析 3. 编写爬虫程序 3.1. 配置需要爬取的目标变量 3.2. 写爬虫文件scrapy 3.3. 编写yield需要的管道文件 3.4. setting中配置请求 ...

  9. 爬虫系列4:scrapy技术进阶之多页面爬取

    多页面爬取有两种形式. 1)从某一个或者多个主页中获取多个子页面的url列表,parse()函数依次爬取列表中的各个子页面. 2)从递归爬取,这个相对简单.在scrapy中只要定义好初始页面以及爬虫规 ...

随机推荐

  1. jxl的API

    jxl的API 使用Windows操作系统的朋友对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事.在Web应用日益盛行的今天,通过Web来操作Exce ...

  2. cocos2d-x 3.x丨搭建Android环境下的开发环境

    所需要的一些工具软件: 1.JDK  官网下载地址:http://www.oracle.com/ttechnetwork/java/javase/downloads/index.html 2.Andr ...

  3. Windows环境Mycat数据库分库分表中间件部署

    下载地址MYCAT官方网站 jdk安装配置 首先去oracle官网下载并安装jdk8,添加环境变量,JAVA_HOME设置为D:\Worksoftware\Java\jdk1.8 CLASSPATH设 ...

  4. Flex4 双选下拉列表的实现(源代码)

    本文属原创文章,如需转载,请注明出处,谢谢 企业应用中少不了双选下拉列表控件,但几乎都没有独立的控件,Flex在这上面得天独厚,ArrayCollection的过滤功能使得我们只需要一个数据源就可以将 ...

  5. OSGI.NET mainfest.xml 配置

    在使用 OSGI.NET进行插件式的开发时,需要对 Mainfest.xml 进行配置, Mainfest 文件是插件的重要配置文件,其中暴露了插件启动方式以及插件启动时所依赖的程序集或其它资源的信息 ...

  6. ndk-gdb 对java/native code联合调试(升级版)

    之前写过一篇 关于android native 开发,调试的文章(http://www.cnblogs.com/yaozhongxiao/archive/2012/03/13/2393959.html ...

  7. oracle学习系列之三 (约束)

    主键约束:外键约束:唯一性约束:检查约束:默认值约束 -——————五大约束 一. 主键约束: --创建表的主键约束  create table student (student_id number ...

  8. uinavigation样式

    UINavigationController* pNav = [[UINavigationController alloc] initWithRootViewController:pView]; [p ...

  9. 多文件目录下makefile文件递归执行编译所有c文件

    首先说说本次嵌套执行makefile文件的目的:只需make根目录下的makefile文件,即可编译所有c文件,包括子目录下的. 意义:自动化编译行为,以后编译自己的c文件时可把这些makefile文 ...

  10. javascript代码复用模式

    代码复用有一个著名的原则,是GoF提出的:优先使用对象组合,而不是类继承.在javascript中,并没有类的概念,所以代码的复用,也并不局限于类式继承.javascript中创建对象的方法很多,有构 ...