关于Scrapy工作过程会在之后添加。
a)首先要已经完成Scrapy的配置安装(如果没有安装,可以参考
Scrapy安装过程
);
b)了解XPath的基本用法(可以参考
XPath教程
);
c)Chrome的审查元素功能也很好用,但是FF的Firebug个人感觉用着更舒服,推荐使用;
d)抓取过程就是发出页面请求,将页面下载之后再使用相关的选择器(HTML或者XML)通过各个XPath选择出相关的内容;
e)目标:抓取每条公交线路的名称以及经过的所有公交站点;
首先来看赶集网的公交页面:
非常简单的一个页面,再看看源代码:
结构也是很清晰。
所需要做的就是找到对应的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()
至此,“寻路”工作已经完成,接下来就是编写对应的爬虫代码。
首先是创建项目,使用命令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路的站点数据就是空的。
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列表
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
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
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文件,添加之前提到的两个配置项。
[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
。