PythonTip >> 博文 >> Scrapy

Scrapy使用——抓取赶集网北京公交信息

zihua 2014-01-18 20:01:44 点击: 1036 | 收藏


关于Scrapy工作过程会在之后添加。

 

0.相关信息


a)首先要已经完成Scrapy的配置安装(如果没有安装,可以参考 Scrapy安装过程 );
b)了解XPath的基本用法(可以参考 XPath教程 );
c)Chrome的审查元素功能也很好用,但是FF的Firebug个人感觉用着更舒服,推荐使用;
d)抓取过程就是发出页面请求,将页面下载之后再使用相关的选择器(HTML或者XML)通过各个XPath选择出相关的内容;
e)目标:抓取每条公交线路的名称以及经过的所有公交站点;

 

1.基本分析


首先来看赶集网的公交页面:

非常简单的一个页面,再看看源代码:

结构也是很清晰。

所需要做的就是找到对应的XPath,找到需要的URL或者数据,让自己编写的爬虫类能够向更深层次的页面前进,最终到达包含有价值的数据的页面。
打开1指向的页面:

打开101路电车指向的页面:

从上页面可以看到,赶集网的结构是首页->线路类别->线路列表->具体线路。那么从首页我们要找到的XPath就是线路类别位置的XPath,使用Firebug可以方便的获得XPath,但是这样获得XPath很长,个人感觉只是比较有参考价值,实际使用中还需要简化。从后面的代码也可以看出,XPath太长实际上很影响维护和代码的精简性(部分XPath为了减少时间没有精简)。

由上图,我们可以看出我们所需要的下一页面的入口各个< a >标签的href属性的值。其中对于上页面中的XPath,Firebug结果为:
/html/body/div[6]/ul/li/a
但是实际上使用的是:
/html/body/div[6]/ul/li[1]/a/@href
原因在于首页的公交站点列表和公交线路列表位于同一个div下,所以我们实际上需要的是第一个li节点下的内容,加上我们需要的是进入下一级页面的url,实际上就是需要取出href属性的值,之后与http://bj.ganji.com合并成为我们所需要的url。
之后再看每个线路类别的页面:

需要获取哪一类对象的XPath直接使用右键->使用Firebug查看元素

之后根据获得的XPath做一些修改之后,我们使用如下的XPath:
//div[@class="bus_tapy_3"]/ul/li/a/@href
到了具体的公交线路页面,我们需要的是公交线路,以及具体经过的公交站点,那么对应内容的XPath为:
公交线路名称:/html/body/div[2]/div[2]/div/div/dl/dt/label/text()
公交经过站点:/html/body/div[2]/div[2]/div/div/dl/dd[2]/a/text()
至此,“寻路”工作已经完成,接下来就是编写对应的爬虫代码。

 

2.代码编写


首先是创建项目,使用命令scrapy startproject gjbus创建一个名为gjbus的项目。
之后进入gjbus/gjbus/spiders目录下,编写相关的爬虫代码:

这里有两点值得注意,首先是文件首行的
# -*- coding: utf-8 -*-
这个的主要目的是让shell中显示的中文不是已\uXXXX形式出现,能够显示出正确的网页内容。Scrapy默认使用UTF-8。
其次是保存文件使用utf-8编码(即fp = codecs.open(‘record.txt’, ‘w’, ‘utf-8′)),这样可以省去编码转换的时间(对于Windows上使用scrapy更是如此)。

简单的代码编写实际上是有套路的:
a)首先一个爬虫类不管名字是什么,其中的name属性值是一定要设定的,原因在于之后爬取时的启动命令scrapy crawl使用的是这里指定的爬虫名字来启动爬虫。
b)之后还要设定start_urls对象,这是一个序列,爬虫启动时首先抓取这些页面,调用默认的回调函数parse进行相关处理。
c)在每个回调函数之中,主要的工作就是相关数据的抽取,抽取是通过对传入的Response对象response使用HtmlXPathSelector通过使用相关的XPath抽取出对应的内容实现的,上面寻找的XPath在这里使用。。
d)还是在回调函数之中,在从起始页面获得进入下一页面的入口url之后,通过创建Request对象将新的抓取请求建立,设定其url和回调函数,之后将这些对象返回,Scrapy相关的调度模块会在之后重新进行抓取工作。
e)对于简单的爬虫程序,直接在回调函数保存结果到文件即可,如果需要进一步的处理,可以再使用pipeline和item进行实现,这里的程序自然用不到。

代码编写完成之后,自然就是抓取了,然而在抓取过程中需要注意不能让爬虫肆意的抓取,在不限制速度情况下,Scrapy对服务器会造成很大的压力,这样有可能使得服务器禁止爬虫访问。限速的一个简单方法就是随机化的延迟发出页面请求的时间,减小爬虫被服务器拒绝的概率。方法是在项目目录下的settings.py文件中增加两个配置项:

1
2
DOWNLOAD_DELAY = 0.5
RANDOMIZE_DOWNLOAD_DELAY = True

其中DOWNLOAD_DELAY = 0.5表示延迟请求500ms,而RANDOMIZE_DOWNLOAD_DELAY = True表示随机化延迟时间,效果是使延迟时间变成DOWNLOAD_DELAY的0.5~1.5倍,所以这两个配置项是需要配套使用的,如果DOWNLOAD_DELAY没有设定,那么是起不到效果的。

使用scrapy crawl gjbus命令开始抓取(里面的0.5和True可以无视):

最后的抓取的文件为:

至此,抓取结束。
赶集网的数据似乎有些缺失,比如375路的站点数据就是空的。

 

3.爬虫相关代码

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# -*- coding: utf-8 -*-
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request
from scrapy.conf import settings
import codecs
#使用UTF-8格式保存文件
fp = codecs. open ( 'record.txt' , 'w' , 'utf-8' )
class BusSpider(BaseSpider):
     #设置爬虫名称
     name = "gjbus"
     #设置起始URL列表
     start_urls = [ "http://bj.ganji.com/bus" ]
     def parse( self , response):
         req = []
         hxs = HtmlXPathSelector(response)
         #通过XPath选出公交车路线分类页面的URL
         cat_urls =   hxs.x( '/html/body/div[6]/ul/li[1]/a/@href' ).extract()
         print 'cat_urls =' , cat_urls
         for url in cat_urls:
             #构建新的URL
             new_url = "http://bj.ganji.com" + url
             print "[parse]new_url = %s" % (new_url)
             #创建对应的页面的Request对象,设定回调函数为parse_cat,利用parse_cat处理返回的页面
             r = Request(new_url, callback = self .parse_cat)
             req.append(r)
         return req
     def parse_cat( self , response):
         req = []
         hxs = HtmlXPathSelector(response)
         #通过XPath选出每一条公交路线的URL
         line_urls = hxs.x( '//div[@class="bus_tapy_3"]/ul/li/a/@href' ).extract()
         cat = response.url.split( '/' )[ - 1 ]
         print "cat =" , cat
         print "line_urls =" , line_urls
         for url in line_urls:
             #构建新的URL
             new_url = "http://bj.ganji.com" + url
             print "[parse_cat]new_url = %s" % (new_url)
             #创建对应的页面的Request对象,设定回调函数为parse_line,利用parse_line处理返回的页面
             r = Request(new_url, callback = self .parse_line)
             req.append(r)
         return req
     def parse_line( self , response):
         hxs = HtmlXPathSelector(response)
         #利用XPath抽取出线路名称
         line_name = hxs.x( '/html/body/div[2]/div[2]/div/div/dl/dt/label/text()' ).extract()
         #利用XPath抽取路线中经过的站点名称
         route = hxs.x( '/html/body/div[2]/div[2]/div/div/dl/dd[2]/a/text()' ).extract()
         #结果写入到记录的文件之中
         fp.write(line_name[ 0 ].strip())
         fp.write( '\r\n' )
         opt = ''
         for name in route:
             opt + = (name.strip() + '|' )
         opt = opt[: - 1 ]
         fp.write(opt)
         fp.write( '\r\n' )
         print "#####"
         print line_name[ 0 ].strip()
         print opt
         print "#####"

 

这里的代码可以直接使用,只需在项目目录下的spiders目录中新建一个.py文件,之后粘贴代码即可,如果需要限速,则还需要修改项目中已经生成的settings.py文件,添加之前提到的两个配置项。

 

4.留白


[scrapy 的架构]

输入抓取命令后,程序自动start_urls开始,将其中的每个url用parse函数处理,parse函数处理后,从Parse函数(或其回调函数)返回两种类型:item(交给item pipeline处理,可以在这里保存数据,或直接用命令scrapy crawl dmoz -o items.json -t json运行怕从,程序会自动以json方式保存手),或新的url抓取request,交给schedule模块安排新一次的抓取(这是迭代抓取数据的关键)。如果在一个parse函数(或其回调函数)中同时返回url或item,用yield代替return,关于yield,具体参见: Yield on Stackoverflow

原文链接:http://www.tuicool.com/articles/VRn6Vz

作者:zihua | 分类: Scrapy | 标签: scrapy | 阅读: 1036 | 发布于: 2014-01-18 20时 |