Scrapy爬虫笔记之基本流程介绍
Scrapy爬虫笔记之基本流程介绍
本博客地址【http://blog.csdn.net/xiantian7】
1.一个项目的基本流程
创建新项目
scrapy startproject <name>
会自动生成目录:
熟悉一下目录结构:
lawson
├── lawson
│ ├── __init__.py
│ ├── items.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ └── __init__.py
└── scrapy.cfg
-
items.py
定义抓取结果中单个项所需要包含的所有内容,比如便利店的地址、分店名称等。【目标】 -
pipelines.py
定义如何对抓取到的内容进行再处理,例如输出文件、写入数据库等。 【处理】 -
settings.py
是 scrapy 的设置文件,可对其行为进行调整。【设置】 -
spiders
目录下存放写好的 spider,也即是实际抓取逻辑。【工具】 -
scrapy.cfg
是整个项目的设置,主要用于部署 scrapyd 服务,本文不会涉及。
各个部分
核心:scrapy 中最为重要的部分就是spider。它包含了分析网页与抓取网页数据的具体逻辑,也就是说对网页上任何内容的任何处理都在 spider中实现。因此,这是 scrapy 整个框架的核心。
item【是用来装搜索得到数据的容器】
Items将会用下载来的数据填满,要创建我们自己的item的时候, 需要
1、导入scrapy.item.Item
2、定义scrapy.item.Field ,通过我们自己的定义,我们就为我们的容器定制了一个”模型“
from scrapy.item import Item, Field
class ConvStore(Item):
name = Field()
branch = Field()
alias = Field()
address = Field()
city = Field()
district = Field()
longitude = Field(serializer=float)
latitude = Field(serializer=float)
这里定义了一个便利店(
ConvStore
)所应包含的内容(Field ),会在spider 中用到,用来承载其抓取下来的实际数据。scrapy的 Item类的行为类似于Python里面的dictionary,你能从中获取key和value。
spider 【数据抓取的主要逻辑】
在spider(名字可以自己定)这个文件中初始化了将要下载的URL,定义了怎么跟踪这些链接,怎么提取每个链接下页面内容有用数据。
如果需要创建一个我们自己的抓取类(spider),需要:
1、继承scrapy.spider.Spider【下面的例子是继承的更强大的基类,所以方式稍有区别,但是都是需要继承一个基类】
2,定义name:【spider的标识】; start_urls【一个需要爬取的链接起始列表】;parse()【这个方法当url的每个下载Response对象产生是被调用,Response是传入这个方法的唯一参数】——例子中自己定义了一个方法,并指定为回调函数,作用是一样的
parse() 方法是用来处理Response对象,返回爬取得数据(以前面定义的Item类型返回),并且获得更多等待爬取的链接
新版的例子
from scrapy.spider import Spider
class DmozSpider(Spider):
name = "dmoz"
allowed_domains = ["dmoz.org"]
start_urls = ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
"http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]
def parse(self, response):
filename = response.url.split("/")[-2]
open(filename, ’wb’).write(response.body)
这上面都干嘛了:
1、scarpy 为每一个在start_urls中的url创建了一个对应的crapy.http.Request请求,并把这个请求放到前面讲的【任务队列】里面,并指定parse方法来处理这些请求,parse为回调函数
2、这些请求被执行,scrapy.http.Response作为执行结果被返回,通过parse()返回这个捕捉类【spider】
需要从页面中提取我们需要的数据,需要用到scrapy中的寻找机制,这种机制基于XPath or CSS,在scrapy里面被称为Scrapy Selectors
这是一些XPath表达式的例子和他们的含义
- /html/head/title: 选择HTML文档<head>元素下面的<title> 标签。
- /html/head/title/text(): 选择前面提到的<title> 元素下面的文本内容
- //td: 选择所有 <td> 元素
- //div[@class="mine"]: 选择所有包含 class="mine" 属性的div 标签元素
必须通过一个 Response 对象对他们进行实例化操作。
你会发现Selector对象展示了文档的节点结构。因此,第一个实例化的selector必与根节点或者是整个目录有关 。
在Scrapy里面,Selectors 有四种基础的方法
- xpath():返回一系列的selectors,每一个select表示一个xpath参数表达式选择的节点
- css():返回一系列的selectors,每一个select表示一个css参数表达式选择的节点
- extract():返回一个unicode字符串,为选中的数据
- re():返回一串一个unicode字符串,为使用正则表达式抓取出来的内容
这是一个复杂点的例子,和上面的ITems类是一致的,继承的基类不一样,多出了写更高级的功能
#coding=utf-8 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.selector import HtmlXPathSelector from lawson.items import ConvStore #引用自己定义的ConvStore类 class LawsonSpider(CrawlSpider): name = 'lawson'#爬虫名 start_urls = ['http://www.lawson.com.cn/shops']#spider 的入口,即是告诉它该从哪个网页开始抓取 allowed_domains = ['lawson.com.cn']# 限定 spider 的抓取活动只能在指定的 domain 中进行 #rules定义了一系列规则用来匹配网页中出现的内容,并根据规则分发至不同的处理方法中(parse_store_list) #SgmlLinkExtractor用来提取链接 #allow 参数是正则表达式,网页中匹配的链接会被抓取 #tags 指定从哪些标签抓取链接,默认 ['a', 'area'](通过分析网页 这里不能包含 area,故手动指定 rules = ( Rule(SgmlLinkExtractor(allow=r'list\?area_id=\d+', tags='a'), callback='parse_store_list'), ) #parse_store_list 方法定义了如何抓取特定网页中的数据 #HtmlXPathSelector 是一个选择器,使用它能方便地定位到网页中的某个位置并抓取其中内容 def parse_store_list(self, response): hxs = HtmlXPathSelector(response) store_selectors = hxs.select('//div[@class="ShopList"]/table/tr')[1:] for s in store_selectors: store = ConvStore() store['name'] = u'罗森' store['alias'] = u'Lawson' store['branch'] = s.select('th/p/text()').extract() store['address'] = s.select('td/span/text()').extract() store['district'] = response.meta['link_text'] store['city'] = u'上海' yield store.load_item()
"XPath 是一种用于从XML选择节点的语言,它也可以被用于HTML"。
我们一般会基于一个定义好的Xpath来告诉 scrapy 到哪里去开始寻找数据. 让我们浏览我们的 Hacker News 站点
'//td[@class="title"]' 是在说: 所有的 <td> 元素中, 如果一个 <a class="title"></a> 被展现了出来,那就到 <td> 元素里面去寻找那个拥有一个被称作title的类型的<a>元素..
CrawlSpider
这个类是整个抓取逻辑的基础,他的工作流程如下:
-
若有
start_urls
,则从这些 URL 开始抓取,若没有,则执行start_requests
方法(用户须定义),并请求该方法返回的Request
对象,并从这些请求结果中开始抓龋 -
所有网页请求返回的
Response
默认交给parse
方法处理。-
parse
方法在CrawlSpider
的默认实现是用已定义的rules
对获得的网页内容进行匹配并进行由Rule
所指定的进一步处理(即交给callback
参数所指定的callable
去处理)。 -
若不指定
callback
,Rule
的默认处理是对匹配的网址发起请求,并再次交给parse
。
-
-
任何方法中返回的 Item 实例(如示例中的
ConvStore
)都会被作为有效数据保存(输出文件等),再处理(Pipeline)。
HtmlXPathSelector
这是一个通过 XPath 对 HTML 页面进行结构化定位和内容读取的工具。scrapy 使用它定位到网页中用户所需要的数据并进行抓龋
def parse_store_list(self, response):
hxs = HtmlXPathSelector(response)
store_selectors = hxs.select('//div[@class="ShopList"]/table/tr')[1:]
for s in store_selectors:
...
store.add_value('branch', s.select('th/p/text()').extract())
store.add_value('address', s.select('td/span/text()').extract())
...
-
HtmlXPathSelector
需要一个Response对象来实例化。 -
//div[@class="ShopList"]/table/tr
选择了所有包含罗森门店信息的tr
标签。 -
s.select('th/p/text()').extract()
在之前的选择基础上继续对其子标签做选择,这里就确实得选择到了分店名。extract()
则将该标签的文本数据读取出来。 -
HtmlXPathSelector
还有正则表达式接口,后文会提到。
Field, Item 及 Item Loader
Field 仅仅是一个 dict
的 wrapper 类,因此使用方法与dict
完全一样,在scrapy 中它负责声明单个 Item 的字段及该字段的各种行为(如序列化方法serializer
)。
Item 用 Field 定义了单个有效数据的具体字段,而实际中则是主要有两种方法写入数据:
-
使用其类似
dict
的接口进行数据的写入和读取,key
为字段名。 - 使用 Item Loader。
dict
接口的用法如上所示很简单,这里说一下 Item Loader。
from scrapy.contrib.loader import ItemLoader
from scrapy.contrib.loader.processor import Compose
from lawson.items import ConvStore
class StoreLoader(ItemLoader):
default_output_processor = Compose(lambda v: v[0], unicode.strip)
def branch_in(self, values):
for v in values:
v = v.strip()
yield v + u'店' if not v.endswith(u'店') else v
class LawsonSpider(CrawlSpider):
...
def parse_store_list(self, response):
hxs = HtmlXPathSelector(response)
store_selectors = hxs.select('//div[@class="ShopList"]/table/tr')[1:]
for s in store_selectors:
store = StoreLoader(item=ConvStore(), response=response)
store.add_value('name', u'罗森')
store.add_value('alias', u'Lawson')
store.add_value('branch', s.select('th/p/text()').extract())
store.add_value('address', s.select('td/span/text()').extract())
store.add_value('district', response.meta['link_text'])
store.add_value('city', u'上海')
yield store.load_item()
Item Loader 的主要作用是对抓取数据的各个字段进行特殊处理,在这里我们定义了一个StoreLoader
类继承(Inherit)自ItemLoader
:
-
default_output_processor
定义默认的输出处理器,这里我们对抓取的数据值进行strip 操作。 -
branch_in
方法是对branch
字段的特殊处理,它发生在输入的时候,也就是刚抓取到数据之后。这里的处理是为没有店
这个字的分店名补上这个字。 -
<field>_in
和<field>_out
会各对指定字段做一次处理,前者是在刚抓取到数据时,后者是在最终输出之前,用户根据需要定义相应方法。- scrapy 有一些 built-in processor可以直接使用,进行一些通用处理。
-
add_value
将值赋予相应字段,很好理解。 -
load_item
返回该条填充过数据的 Item。
使用 Item Loader 的好处显而易见,我们有一个统一的地方对所有数据字段进行处理,不用将其混入抓取逻辑,使整个流程分工明确。【统一处理】
另一个常用的 Item Loader 是XPathItemLoader
,显然这个版本利用了 XPath:
store = XPathItemLoader(item=ConvStore(), response=response)
store.add_xpath('branch', '//div[@class="ShopList"]/table/tr[2]/th/p/text()')
它将字段与 XPath 表达式关联起来,直接完成定位、读取和写入数据的操作,很方便。
加上经纬度
经纬度对于定位一个地点是很有用的,通过电子地图能够精确地定位至相关地点。我发现罗森网站提供了这个信息,但它并未明文显示,而是需要通过其所链接到的百度地图的页面中去抓取下来,听起来很麻烦,但实际却很简单。
from urlparse import urljoin
from scrapy.contrib.loader import XPathItemLoader
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
from scrapy.utils.response import get_base_url
from poi_scrape.items import ConvStore
class StoreLoader(XPathItemLoader):
...
class LawsonSpider(BasePoiSpider):
...
def parse_geo(self, response):
hxs = HtmlXPathSelector(response)
store = response.meta['store']
lng, lat = hxs.re(r'(\d+\.\d+),(\d+\.\d+)')
store.add_value('latitude', lat)
store.add_value('longitude', lng)
return store.load_item()
def parse_store_list(self, response):
...
for s in store_selectors:
store = StoreLoader(item=ConvStore(), response=response)
...
map_rel_url = s.select('td/a/@rel').extract()
if map_rel_url:
map_url = urljoin(get_base_url(response), map_rel_url[0])
req = Request(map_url, callback=self.parse_geo)
req.meta['store'] = store
yield req
else:
yield store.load_item()
这里为 LawsonSpider
新增了一个方法 parse_geo
,同时改写了parse_store_list
。
-
在
parse_store_list
的循环中我抓取每个店的tr
标签中的td/a/@rel
属性(Attribute)(这里@rel
表示a
标签的rel
属性),若有这一属性则对这个地图的链接发起请求,即yield req
。-
在 scrapy 中,spider 类中的方法若返回 Request 实例则 scrapy 会自动对该Request 包含的 URL 发出请求,并将其返回的结果封装为 Response 后交给
callback
参数中指定的方法处理,若未指定callback
,则交给parse
方法处理。
-
在 scrapy 中,spider 类中的方法若返回 Request 实例则 scrapy 会自动对该Request 包含的 URL 发出请求,并将其返回的结果封装为 Response 后交给
-
req.meta['store'] = store
,每个 Request 有一个预定义的meta
属性(dict
),保存在其中的值在其对应的 Response 中可以再次取出:store = response.meta['store']
。【meta:英文意思为在其中】 -
hxs.re(r'(\d+\.\d+),(\d+\.\d+)')
使用了HtmlXPathSelector
的正则表达式接口直接从网页中通过正则表达式匹配抓取数据。
完整 spider 代码
from urlparse import urljoin from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from scrapy.contrib.loader import XPathItemLoader from scrapy.contrib.loader.processor import Compose from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.http import Request from scrapy.selector import HtmlXPathSelector from scrapy.utils.response import get_base_url from lawson.items import ConvStore class StoreLoader(XPathItemLoader): default_output_processor = Compose(lambda v: v[0], unicode.strip) def branch_in(self, values): for v in values: v = v.strip() yield v + u'店' if not v.endswith(u'店') else v class LawsonSpider(CrawlSpider): name = 'lawson' start_urls = ['http://www.lawson.com.cn/shops'] allowed_domains = ['lawson.com.cn'] rules = ( Rule(SgmlLinkExtractor(allow=r'list\?area_id=\d+', tags='a'), callback='parse_store_list'), ) def parse_geo(self, response): hxs = HtmlXPathSelector(response) store = response.meta['store'] lng, lat = hxs.re(r'(\d+\.\d+),(\d+\.\d+)') store.add_value('latitude', lat) store.add_value('longitude', lng) return store.load_item() def parse_store_list(self, response): hxs = HtmlXPathSelector(response) store_selectors = hxs.select('//div[@class="ShopList"]/table/tr')[1:] for s in store_selectors: store = StoreLoader(item=ConvStore(), response=response) store.add_value('name', u'罗森') store.add_value('alias', u'Lawson') store.add_value('branch', s.select('th/p/text()').extract()) store.add_value('address', s.select('td/span/text()').extract()) store.add_value('district', response.meta['link_text']) store.add_value('city', u'上海') map_rel_url = s.select('td/a/@rel').extract() if map_rel_url: map_url = urljoin(get_base_url(response), map_rel_url[0]) req = Request(map_url, callback=self.parse_geo) req.meta['store'] = store yield req else: yield store.load_item()
文章参考:
1、http://kanedou.me/2012/07/introducing-python-scrapy-part-i/
2、http://blog.csdn.net/pleasecallmewhy/article/details/19642329
3、http://www.xefan.com/archives/83883.html
4、http://www.linuxeden.com/html/news/20140102/147107.html 【提到了将数据存储在数据库中】