摘要:介绍了使用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. maven配置开发

    1.项目中使用Log4j对其日志进行配置管理,采取的方式一种是通过properties文件设置,另一种方式就是通过设置xml文件的配置. 使用场景: 编程模型:log.err();log.debug( ...

  2. ASP.NET fails to detect Internet Explorer 10

    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9"> http://www.han ...

  3. 004.ASP.NET MVC中的HTML Helpers

    原文链接:http://www.codeproject.com/Articles/794579/ASP-NET-MVC-HTML-Helpers-A-MUST-KNOW 1.什么是HTML Helpe ...

  4. Laxcus大数据管理系统2.0(13)- 总结

    总结 以上从多个角度阐述了Laxcus主要组成部分和应用情况.所有设计都是基于现实环境下的评估.对比.测试和考量.设计的基本思路很明确,就是将各项功能分解.细化.归类,形成一个个可以独立.小的模块,每 ...

  5. 学习练习 java 实例属性 静态属性

    package com.hanqi; public class Test11Car11 { //静态 //实例属性 private int m = 0; //静态属性 //所有实例共有的,在内存里只有 ...

  6. hadoop2.5.1搭建(一)

    1.1配置 1.1.1修改hosts vi /etc/hosts 192.168.220.64 cluster4 192.168.220.63 cluster3 1.2安装jdk rpm安装 rpm ...

  7. WF4.0 自定义CodeActivity与Bookmark<第三篇>

    一.自定义CodeActivity CodeActivity用于自定义一段代码,可实现你自己写的任意功能. 要注意的有两点: 1.自定义CodeActivity必须继承自CodeActivity; 2 ...

  8. 苹果系列机型专业刷机,解锁,解ID

    如有软件开发需求,请留言或在猪八戒网主页留言http://home.zhubajie.com/8506525/,常年接收c.c++(vs2010.RAD studio xe5\RAD studio 2 ...

  9. javaSE第十一天

    第十一天    63 1:Eclipse的概述使用(掌握)    63 2:API的概述(了解)    63 3:Object类(掌握)    63       第十一天 1:Eclipse的概述使用 ...

  10. C\C++ 框架和库整理(转)

    [本文系外部转贴,原文地址:http://coolshell.info/c/c++/2014/12/13/c-open-project.htm]留作存档 下次造轮子前先看看现有的轮子吧 值得学习的C语 ...