安装部署Scrapy
在安装Scrapy前首先需要确定的是已经安装好了Python(目前Scrapy支持Python2.5,Python2.6和Python2.7)。官方文档中介绍了三种方法进行安装,我采用的是使用 easy_install 进行安装,首先是下载Windows版本的setuptools(下载地址:http://pypi.python.org/pypi/setuptools),下载完后一路NEXT就可以了。
安装完setuptool以后。执行CMD,然后运行一下命令:
1
|
easy_install -U Scrapy |
同样的你可以选择使用pip安装,pip的地址:http://pypi.python.org/pypi/pip
使用pip安装Scrapy的命令为
1
|
pip install Scrapy |
如果你的电脑先前装过visual studio 2008 或 visual studio 2010那么一起顺利,Scrapy已经安装完成。如果出现下列报错:Unable to find vcvarsall.bat 那么你需要折腾下。你可以安装visual studio 后进行安装或采用下面的方式进行解决:
首先安装MinGW(MinGW下载地址:http://sourceforge.net/projects/mingw/files/),在MinGW的安装目录下找到bin的文件夹,找到mingw32-make.exe,复制一份更名为make.exe;
把MinGW的路径添加到环境变量path中,比如我把MinGW安装到D:\MinGW\中,就把D:\MinGW\bin添加到path中;
打开命令行窗口,在命令行窗口中进入到要安装代码的目录下;
输入如下命令 setup.py install build –compiler=mingw32 就可以安装了。
如果出现“xslt-config' 不是内部或外部命令,也不是可运行的程序或批处理文件。”错误,原因主要是lxml安装不成功,只要上http://pypi.python.org/simple/lxml/下载个exe文件进行安装就可以了。
下面就可以进入正题了。
新建工程
让我们来用爬虫获取豆瓣电影Top 250的电影信息吧。开始之前,我们新建一个Scrapy工程。因为我用的Win7,所以在CMD中进入一个我希望保存代码的目录,然后执行:
1
|
D:\WEB\Python>scrapy startproject doubanmoive |
这个命令会在当前目录下创建一个新的目录doubanmoive,目录结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
D:\WEB\Python\doubanmoive>tree /f Folder PATH listing for volume Data Volume serial number is 00000200 34EC:9CB9 D:. │ scrapy.cfg │ └─doubanmoive │ items.py │ pipelines.py │ settings.py │ __init__.py │ └─spiders __init__.py |
这些文件主要为:
- doubanmoive/items.py: 定义需要获取的内容字段,类似于实体类。
- doubanmoive/pipelines.py: 项目管道文件,用来处理Spider抓取的数据。
- doubanmoive/settings.py: 项目配置文件
- doubanmoive/spiders: 放置spider的目录
定义项目(Item)
Item是用来装载抓取数据的容器,和Java里的实体类(Entity)比较像,打开doubanmoive/items.py可以看到默认创建了以下代码。
1
2
3
4
|
from scrapy.item import Item, Field class DoubanmoiveItem(Item): pass |
我们只需要在 Doubanmoive 类中增加需要抓取的字段即可,如 name=Field() ,最后根据我们的需求完成代码如下。
1
2
3
4
5
6
7
8
9
|
from scrapy.item import Item, Field class DoubanmoiveItem(Item): name = Field() #电影名 year = Field() #上映年份 score = Field() #豆瓣分数 director = Field() #导演 classification = Field() #分类 actor = Field() #演员 |
编写爬虫(Spider)
Spider是整个项目中最核心的类,在这个类里我们会定义抓取对象(域名、URL)以及抓取规则。Scrapy官方文档中的教程是基于 BaseSpider 的,但 BaseSpider 只能爬取给定的URL列表,无法根据一个初始的URL向外拓展。不过除了 BaseSpider ,还有很多可以直接继承 Spider 的类,比如 scrapy.contrib.spiders.CrawlSpider 。
在 doubanmoive/spiders 目录下新建moive_spider.py文件,并填写代码。
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
|
# -*- coding: utf-8 -*- from scrapy.selector import Selector from scrapy.contrib.spiders import CrawlSpider,Rule from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from doubanmoive.items import DoubanmoiveItem class MoiveSpider(CrawlSpider): name = "doubanmoive" allowed_domains = [ "movie.douban.com" ] start_urls = [ "http://movie.douban.com/top250" ] rules = [ Rule(SgmlLinkExtractor(allow = (r 'http://movie.douban.com/top250\?start=\d+.*' ))), Rule(SgmlLinkExtractor(allow = (r 'http://movie.douban.com/subject/\d+' )),callback = "parse_item" ), ] def parse_item( self ,response): sel = Selector(response) item = DoubanmoiveItem() item[ 'name' ] = sel.xpath( '//*[@id="content"]/h1/span[1]/text()' ).extract() item[ 'year' ] = sel.xpath( '//*[@id="content"]/h1/span[2]/text()' ).re(r '\((\d+)\)' ) item[ 'score' ] = sel.xpath( '//*[@id="interest_sectl"]/div/p[1]/strong/text()' ).extract() item[ 'director' ] = sel.xpath( '//*[@id="info"]/span[1]/a/text()' ).extract() item[ 'classification' ] = sel.xpath( '//span[@property="v:genre"]/text()' ).extract() item[ 'actor' ] = sel.xpath( '//*[@id="info"]/span[3]/a[1]/text()' ).extract() return item |
代码说明: MoiveSpider 继承Scrapy中的 CrawlSpider , name , allow_domains , start_url 看名字就知道什么含义,其中rules稍微复杂一些,定义了URL的抓取规则,符合 allow 正则表达式的链接都会加入到Scheduler(调度程序)。通过分析豆瓣电影Top250的分页URL http://movie.douban.com/top250?start=25&filter=&type= 可以得到以下规则
Rule(SgmlLinkExtractor(allow=(r'http://movie.douban.com/top250\?start=\d+.*'))),
而我们真正要抓取的页面是每一个电影的详细介绍,如肖申克的救赎的链接为 http://movie.douban.com/subject/1292052/ ,那只有 subject 后面的数字是变化的,根据正则表达式得到如下代码。我们需要抓取这种类型链接中的内容,于是加入callback属性,将Response交给parse_item函数来处理。
Rule(SgmlLinkExtractor(allow=(r'http://movie.douban.com/subject/\d+')),callback="parse_item"),
在 parse_item 函数中的处理逻辑非常简单,获取符合条件链接的代码,然后根据一定的规则抓取内容赋给item并返回 Item Pipeline 。获取大部分标签的内容不需要编写复杂的正则表达式,我们可以使用 XPath 。 XPath 是一门在 XML 文档中查找信息的语言,但它也可以用在HTML中。下表列出了常用表达式。
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
如 //*[@id="content"]/h1/span[1]/text() 获取的结果是在id为content的任意元素下h1元素下的span列表中第一个元素的文本内容。我们可以通过Chrome开发者工具(F12)来获取某内容的XPath表达式,具体操作为在需要抓取的内容上点击审查元素,下方就会出现开发者工具,并定位到该元素,在内容上点击右键,选择复制XPath。
存储数据
爬虫获取到数据以后我们需要将其存储到数据库中,之前我们提到该操作需要靠项目管道(pipeline)来处理,其通常执行的操作为:
- 清洗HTML数据
- 验证解析到的数据(检查项目是否包含必要的字段)
- 检查是否是重复数据(如果重复就删除)
- 将解析到的数据存储到数据库中
由于我们获取的数据格式多种多样,有一些存储在关系型数据库中并不方便,所以我在写完MySQL版本的Pipeline之后又写了一个MongoDB的。
MySQL版本:
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
|
# -*- coding: utf-8 -*- from scrapy import log from twisted.enterprise import adbapi from scrapy.http import Request import MySQLdb import MySQLdb.cursors class DoubanmoivePipeline( object ): def __init__( self ): self .dbpool = adbapi.ConnectionPool( 'MySQLdb' , db = 'python' , user = 'root' , passwd = 'root' , cursorclass = MySQLdb.cursors.DictCursor, charset = 'utf8' , use_unicode = False ) def process_item( self , item, spider): query = self .dbpool.runInteraction( self ._conditional_insert, item) query.addErrback( self .handle_error) return item def _conditional_insert( self ,tx,item): tx.execute( "select * from doubanmoive where m_name= %s" ,(item[ 'name' ][ 0 ],)) result = tx.fetchone() log.msg(result,level = log.DEBUG) print result if result: log.msg( "Item already stored in db:%s" % item,level = log.DEBUG) else : classification = actor = '' lenClassification = len (item[ 'classification' ]) lenActor = len (item[ 'actor' ]) for n in xrange (lenClassification): classification + = item[ 'classification' ][n] if n<lenClassification - 1 : classification + = '/' for n in xrange (lenActor): actor + = item[ 'actor' ][n] if n<lenActor - 1 : actor + = '/' tx.execute(\ "insert into doubanmoive (m_name,m_year,m_score,m_director,m_classification,m_actor) values (%s,%s,%s,%s,%s,%s)" ,\ (item[ 'name' ][ 0 ],item[ 'year' ][ 0 ],item[ 'score' ][ 0 ],item[ 'director' ][ 0 ],classification,actor)) log.msg( "Item stored in db: %s" % item, level = log.DEBUG) def handle_error( self , e): log.err(e) |
MongoDB版本:
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
|
# -*- coding: utf-8 -*- import pymongo from scrapy.exceptions import DropItem from scrapy.conf import settings from scrapy import log class MongoDBPipeline( object ): #Connect to the MongoDB database def __init__( self ): connection = pymongo.Connection(settings[ 'MONGODB_SERVER' ], settings[ 'MONGODB_PORT' ]) db = connection[settings[ 'MONGODB_DB' ]] self .collection = db[settings[ 'MONGODB_COLLECTION' ]] def process_item( self , item, spider): #Remove invalid data valid = True for data in item: if not data: valid = False raise DropItem( "Missing %s of blogpost from %s" % (data, item[ 'url' ])) if valid: #Insert data into database new_moive = [{ "name" :item[ 'name' ][ 0 ], "year" :item[ 'year' ][ 0 ], "score" :item[ 'score' ][ 0 ], "director" :item[ 'director' ], "classification" :item[ 'classification' ], "actor" :item[ 'actor' ] }] self .collection.insert(new_moive) log.msg( "Item wrote to MongoDB database %s/%s" % (settings[ 'MONGODB_DB' ], settings[ 'MONGODB_COLLECTION' ]), level = log.DEBUG, spider = spider) return item |
可以看到其基本的处理流程是一样,但是MySQL不太方便的一点就是需要将数组类型的数据通过分隔符转换。而MongoDB支持存入List、Dict等多种类型的数据。
配置文件
在运行爬虫之前还需要将在 settings.py 中增加一些配置信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
BOT_NAME = 'doubanmoive' SPIDER_MODULES = [ 'doubanmoive.spiders' ] NEWSPIDER_MODULE = 'doubanmoive.spiders' ITEM_PIPELINES = { 'doubanmoive.mongo_pipelines.MongoDBPipeline' : 300 , 'doubanmoive.pipelines.DoubanmoivePipeline' : 400 , } LOG_LEVEL = 'DEBUG' DOWNLOAD_DELAY = 2 RANDOMIZE_DOWNLOAD_DELAY = True USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5' COOKIES_ENABLED = True MONGODB_SERVER = 'localhost' MONGODB_PORT = 27017 MONGODB_DB = 'python' MONGODB_COLLECTION = 'test' |
ITEM_PIPELINES 中定义了MySQL和MongoDB两个Pipeline文件,后面的数字代表执行的优先级顺序,范围为0~1000。 而中间的 DOWNLOAD_DELAY 等信息是为了防止爬虫被豆瓣Ban掉,增加了一些随机延迟,浏览器代理等。最后的就是MongoDB的配置信息,MySQL也可以参考这种方式来写。
至此为止,抓取豆瓣电影的爬虫就已经完成了。在命令行中执行 Scrapy crawl doubanmoive 让蜘蛛开始爬行吧!