原标题:Python 爬虫:Scrapy 实例(一)

1、创建Scrapy项目

似乎所有的框架,开始的第一步都是从创建项目开始的,Scrapy也不例外。在这之前要说明的是Scrapy项目的创建、配置、运行……默认都是在终端下操作的。不要觉得很难,其实它真的非常简单,做填空题而已。如果实在是无法接受,也可以花点心思配置好Eclipse,在这个万能IDE下操作。推荐还是在终端操作比较好,虽然开始可能因为不熟悉而出现很多错误,错多了,通过排错印象深刻了,也就自然学会了。打开Putty连接到Linux,开始创建Scrapy项目。执行命令:

cd
cd code/scrapy/
scrapy startproject todayMovie
tree todayMovie

执行结果如图1所示。


图1 创建todayMovie项目

tree命令将以树形结构显示文件目录结构。tree命令默认情况下是没有安装的,可以执行命令apt-get install tree来安装这个命令。

这里可以很清楚地看到todayMovie目录下的所有子文件和子目录。至此Scrapy项目todayMovie基本上完成了。按照Scrapy的提示信息,可以通过Scrapy的Spider基础模版顺便建立一个基础的。相当于把填空题打印到试卷上,等待填空了。当然,也可以不用Scrapy命令建立基础,如果非要体验一下DIY也是可以的。这里我们还是怎么简单怎么来吧,按照提示信息,在该终端中执行命令:

cd todayMovie
scrapy genspider wuHanMovieSpider mtime.com

执行结果如图2所示。


图2 创建基础爬虫

至此,一个最基本的项目已经建立完毕了,它包含了一个Scrapy所需的基础文件。到这一步可以说填空题已准备完毕,后面的工作就纯粹是填空了。图2中第一行文字scrapy genspider是一个命令,也是Scrapy最常用的几个命令之一,它的使用方法如图3所示。


图3 scrapy genspider命令帮助

因此,刚才的命令意思是使用scrapy genspider命令创建一个名字为wuHanMovieSpider的爬虫脚本。这个脚本搜索的域为mtime.com。

2、Scrapy文件介绍

Scrapy项目的所有文件都已经到位了,如图2所示,下面来看看各个文件的作用。首先最顶层的那个todayMovie文件夹是项目名,这个没什么好说的。

在第二层中是一个与项目同名的文件夹todayMovie和一个文件scrapy.cfg,这里与项目同名的文件夹todayMovie是模块(也可以叫做包的),所有的项目代码都在这个模块(文件夹或者叫包)内添加。而scrapy.cfg文件,顾名思义它是整个Scrapy项目的配置文件。来看看这个文件里有些什么。Scrapy.cfg文件内容如下:

1 # Automatically created by: scrapy startproject
2 #
3 # For more information about the [deploy] section see:
4 # http://doc.scrapy.org/en/latest/topics/scrapyd.html
5
6 [settings]
7 default = todayMovie.settings
8
9 [deploy]
10 #url = http://localhost:6800/
11 project = todayMovie

除去以“#”为开头的注释行,整个文件只声明了两件事:一是定义默认设置文件的位置为todayMovie模块下的settings文件,二是定义项目名称为todayMovie。

在第三层中有6个文件和一个文件夹(实际上这也是个模块)。看起来很多。实际上有用的也就3个文件,分别是items.py、pipelines.py、settings.py。其他的3个文件中,以pyc结尾的是同名程序编译得到的字节码文件,settings.pyc是settings.py的字节码文件,__init__.pyc是__init__.py的字节码文件。据说用来加快程序的运行速度,可以忽视。至于__init__.py文件,它是个空文件,里面什么都没有。在此处唯一的作用就是将它的上级目录变成了一个模块。也就是说第二层的todayMovie模块下,如果没有__init__.py文件。那么todayMovie就只是一个单纯的文件夹。在任何一个目录下添加一个空的__init__.py文件,就会将该文件夹模块化,可以供导入使用。

有用的这3个文件中。settings.py是上层目录中scrapy.cfg定义的设置文件。settings.py的内容如下:

1 # -*- coding: utf-8 -*-
2
3 # Scrapy settings for todayMovie project
4 #
5 # For simplicity, this file contains only settings considered importantor
6 # commonly used. You can find more settings consulting thedocumentation:
7 #
8 # https://doc.scrapy.org/en/latest/topics/settings.html
9 # https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
10 # https://doc.scrapy.org/en/latest/topics/spider-middleware.html
11
12 BOT_NAME = 'todayMovie'
13
14 SPIDER_MODULES = ['todayMovie.spiders']
15 NEWSPIDER_MODULE = 'todayMovie.spiders'
16
17
18 # Crawl responsibly by identifying yourself (and your website) on the
user-agent
19 #USER_AGENT = 'todayMovie (+http://www.yourdomain.com)'
20
21 # Obey robots.txt rules
22 ROBOTSTXT_OBEY = True

items.py文件的作用是定义爬虫最终需要哪些项,items.py的内容如下:

1 # -*- coding: utf-8 -*-
2
3 # Define here the models for your scraped items
4 #
5 # See documentation in:
6 # http://doc.scrapy.org/en/latest/topics/items.html
7
8 import scrapy
9
10
11 class TodaymovieItem(scrapy.Item):
12 # define the fields for your item here like:
13 # name = scrapy.Field
14 pass

pipelines.py文件的作用是扫尾。Scrapy爬虫爬取了网页中的内容后,这些内容怎么处理就取决于pipelines.py如何设置了。pipeliens.py文件内容如下:

1 # -*- coding: utf-8 -*-
2
3 # Define your item pipelines here
4 #
5 # Don't forget to add your pipeline to the ITEM_PIPELINES setting
6 # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
7
8
9 class TodaymoviePipeline(object):
10 def process_item(self, item, spider):
11 return item

第二层中还有一个spiders的文件夹。仔细看一下,在该目录下也有个__init__.py文件,说明这个文件夹也是一个模块。在该模块下是本项目中所有的爬虫文件。

第三层中有3个文件,__init__.py、__init__.pyc、wuHanMovieSpider.py。前两个文件刚才已经介绍过了,基本不起作用。wuHanMovieSpider.py文件是刚才用scrapy genspider命令创建的爬虫文件。wuHanMovieSpider.py文件内容如下:

1 # -*- coding: utf-8 -*-
2 import scrapy
3
4
5 class WuhanmoviespiderSpider(scrapy.Spider):
6 name = "wuHanMovieSpider"
7 allowed_domains = ["mtime.com"]
8 start_urls = (
9 'http://www.mtime.com/',
10 )
11
12 def parse(self, response):
13 pass

在本次的爬虫项目示例中,需要修改、填空的只有4个文件,它们分别是items.py、settings.py、pipelines.py、wuHanMovieSpider.py。其中items.py决定爬取哪些项目,wuHanMovieSpider.py决定怎么爬,settings.py决定由谁去处理爬取的内容,pipelines.py决定爬取后的内容怎样处理。

3、Scrapy爬虫编写

My first scrapy crawl怎么简单,怎么清楚就怎么来。这个爬虫只爬取当日电影名字,那我们只需要在网页中采集这一项即可。

(1)选择爬取的项目items.py

修改items.py文件如下:

1 # -*- coding: untf-8 -*-
2
3 # Define here the models for your scraped items
4 #
5 # See documentation in:
6 # http://doc.scrapy.org/en/latest/topics/items.html
7
8 import scrapy
9
10
11 class TodaymovieItem(scrapy.Item):
12 # define the fields for your item here like:
13 # name = scrapy.Field
14 #pass
15 movieTitleCn = scrapy.Field #影片中文名
16 movieTitleEn = scrapy.Field #影片英文名
17 director = scrapy.Field #导演
18 runtime = scrapy.Field #电影时长

由于中严格的格式检查。中最常见的异常Indentati会经常出现。如果使用的编辑器是vi或者vim,强烈建议修改vi的全局配置文件/etc/vim/vimrc,将所有的4个空格变成tab。

与最初的items.py比较一下,修改后的文件只是按照原文的提示添加了需要爬取的项目,然后将类结尾的pass去掉了。这个类是继承与Scrapy的Iteam类,它没有重载类的__init__的解析,没有定义新的类,只定义了类成员。

(2)定义怎样爬取wuHanMovieSpider.py

修改spiders/wuHanMovieSpider.py,内容如下:

1 # -*- coding: utf-8 -*-
2 import scrapy
3 from todayMovie.items import TodaymovieItem
4 import re
5
6
7 class WuhanmoviespiderSpider(scrapy.Spider):
8 name = "wuHanMovieSpider"
9 allowed_domains = ["mtime.com"]
10 start_urls = [
11 'http://theater.mtime.com/China_Hubei_Province_Wuhan_Wuchang/4316/',
12 ] #这个是武汉汉街万达影院的主页
13
14
15 def parse(self, response):
16 selector =response.xpath('/html/body/[3]/text')[0].extract
17 moviesStr = re.search('"movies":[.*?]', selector).group
18 moviesList = re.findall('{.*?}', moviesStr)
19 items = []
20 for movie in moviesList:
21 mDic = eval(movie)
22 item = TodaymovieItem
23 item['movieTitleCn'] = mDic.get('movieTitleCn')
24 item['movieTitleEn'] = mDic.get('movieTitleEn')
25 item['director'] = mDic.get('director')
26 item['runtime'] = mDic.get('runtime')
27 items.append(item)
28 return items

在这个文件中,首先导入了scrapy模块,然后从模块(包)todayMovie中的items文件中导入了TodaymovieItem类,也就是刚才定义需要爬行内容的那个类。WuhanmovieSpider是一个自定义的类,它是由scrapy genspider命令自动生成的。这个自定义类继承于scrapy.Spider类。第8行的name定义的是名。第9行的allowed_domains定义的是域范围,也就是说该只能在这个域内爬行。第11行的start_urls定义的是爬行的网页,这个只需要爬行一个网页,所以在这里start_urls可以是一个元组类型。如果需要爬行多个网页,最好使用列表类型,以便于随时在后面添加需要爬行的网页。

类中的parse函数需要参数response,这个response就是请求网页后返回的数据。至于怎么从response中选取所需的内容,一般采取两种方法,一是直接在网页上查看网页源代码,二是自己写个Python 3程序使用urllib.request将网页返回的内容写入到文本文件中,再慢慢地查询。

打开Chrome浏览器,在地址栏输入爬取网页的地址,打开网页。

同一网页内的同一项目格式基本上都是相同的,即使略有不同,也可以通过增加挑选条件将所需的数据全部放入选择器。在网页中右击空白处,在弹出菜单中选择“查看网页源代码”。

打开源代码网页,按Ctrl+F组合键,在查找框中输入“寻梦环游记”后按回车键,查找结果如图4所示。


图4 查找关键词

整个源代码网页只有一个查询结果,那就是它了。而且很幸运的是,所有的电影信息都在一起,是以json格式返回的。这种格式可以很容易地转换成字典格式获取数据(也可以直接json模块获取数据)。

仔细看看怎样才能得到这个“字典”(json格式的字符串)呢?如果嵌套的标签比较多,可以用XPath嵌套搜索的方式来逐步。这个页面的源码不算复杂,直接Tag标签后一个一个的数标签就可以了。Json字符串包含在标签内,数一下标签的位置,在脚本中执行语句:

16 selector = response.xpath('/html/body/[3]/text')[0].extract( )

意思是选择页面代码中html标签下的body标签下的第4个标签。然后获取这个标签的所有文本,并释放出来。选择器的选择到底对不对呢?可以验证一下,在该项目的任意一级目录下,执行命令:

scrapy shell

http://theater.mtime.com/China_Hubei_Province_Wuhan_Wuchang/4316/

执行结果如图5所示。


图5 scrapy shell

response后面的200是网页返回代码,200代表获取数据正常返回,如果出现其他的数字,那就得仔细检查代码了。现在可以放心地验证了,执行命令:

selector =
response.xpath('/html/body/[3]/text')
[0].extract
print(selector)

执行结果如图6所示。


图6 验证选择器

看来选择器的选择没问题。再回头看看wuHanMovieSpider.py中的parse就很容易理解了。代码第17、18行先用re模块将json字符串从选择器的结果中过滤出来。第19行定义了一个items的空列表,这里定义items的列表是因为返回的item不止一个,所以只能让item以列表的形式返回。第22行item初始化为一个TodaymoizeItem的类,这个类是从todayMovie.items中初始化过来的。第21行将json字符串转换成了一个字典格式。第23~26行将已经初始化类item中的movieName项赋值。第27行将item追加到items列表中去。最后return items,注意这里返回的是items,不是item。

(3)保存爬取的结果pipelines.py

修改pipelines.py,内容如下:

1 # -*- coding: utf-8 -*-
2
3 # Define your item pipelines here
4 #
5 # Don't forget to add your pipeline to the ITEM_PIPELINES setting
6 # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
7
8 import codecs
9 import time
10
11 class TodaymoviePipeline(object):
12 def process_item(self, item, spider):
13 today = time.strftime('%Y-%m-%d', time.localtime)
14 fileName = '武汉汉街万达广场店' + today + '.txt'
15 with codecs.open(fileName, 'a+', 'utf-8') as fp:
16 fp.write('%s %s %s %s rn'
17 %(item['movieTitleCn'],
18 item['movieTitleEn'],
19 item['director'],
20 item['runtime']))
21 # return item

这个脚本没什么可说的,比较简单。就是把当日的年月日抽取出来当成文件名的一部分。然后把wuHanMovieSpider.py中获取项的内容输入到该文件中。这个脚本中只需要注意两点。第一,open创建文件时必须是以追加的形式创建,也就是说open的第二个参数必须是a,也就是文件写入的追加模式append。。因为wuHanMovieSpider.py返回的是一个item列表items,这里的写入文件只能一个一个item地写入。如果open的第二个参数是写入模式write,造成的后果就是先擦除前面写入的内容,再写入新内容,一直循环到items列表结束,最终的结果就是文件里只保存了最后一个item的内容。第二是保存文件中的内容如果含有汉字就必须转换成utf8码。汉字的unicode码保存到文件中正常人类都是无法识别的,所以还是转换成正常人类能识别的utf8吧。

到了这一步,这个Scrapy爬虫基本上完成了。回到scrapy.cfg文件的同级目录下(实际上只要是在todayMovie项目下的任意目录中执行都行,之所以在这一级目录执行纯粹是为了美观而已),执行命令:

scrapy crawl wuHanMovieSpider

结果却什么都没有?为什么呢?

(4)分派任务的settings.py

先看看settings.py的初始。它仅指定了Spider的位置。再看看写好的Spider的开头,它导入了items.py作为模块,也就是说现在Scrapy已经知道了爬取哪些项目,怎样爬取内容,而pipelines说明了最终的爬取结果怎样处理。唯一不知道的就是由谁来处理这个爬行结果,这时候就该setting.py出点力气了。setting.py的最终如下:

1 # -*- coding: utf-8 -*-
2
3 # Scrapy settings for todayMovie project
4 #
5 # For simplicity, this file contains only the most important settingsby
6 # default. All the other settings are documented here:
7 #
8 # http://doc.scrapy.org/en/latest/topics/settings.html
9 #
10
11 BOT_NAME = 'todayMovie'
12
13 SPIDER_MODULES = ['todayMovie.spiders']
14 NEWSPIDER_MODULE = 'todayMovie.spiders'
15
16 # Crawl responsibly by identifying yourself (and your website) on theuser-a gent
17 #USER_AGENT = 'todayMovie (+http://www.yourdomain.com)'
18
19 ### user define
20 ITEM_PIPELINES = {'todayMovie.pipelines.TodaymoviePipeline':300}

这跟初始的settings.py相比,就是在最后添加了一行ITEM_PIPELINES。它告诉Scrapy最终的结果是由todayMovie模块中pipelines模块的TodaymoviePipeline类来处理。ITEM_PIPELINES是一个字典,字典的key用来处理结果的类,字典的value是这个类执行的顺序。这里只有一种处理方式,value填多少都没问题。如果需要多种处理结果的方法,那就要确立顺序了。数字越小的越先被执行。

现在可以测试这个Scrapy爬虫了,还是执行命令:

scrapy crawl wuHanMovieSpider
ls
cat *.txt

执行结果如图7所示。


图7 Scrapy爬虫结果

这个最简单的爬虫就到这里了。从这个项目可以看出,Scrapy爬虫只需要顺着思路照章填空就可以了。如果需要的项比较多,获取内容的网页源比较复杂或者不规范,可能会稍微麻烦点,但处理起来基本上都是大同小异的。与re爬虫相比,越复杂的爬虫就越能体现Scrapy的优势。