@nemos
2017-05-06T02:10:38.000000Z
字数 10946
阅读 1196
py
$ scrapy startproject tutorial # 初始化一个项目
import scrapy
class MySpider(scrapy.Spider):
name = "myspider" # 爬虫名,必须唯一
start_urls = []
# 初始化的url,上下选其一
def start_requests(self):
urls = []
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
#必须返回一个Request对象的迭代器,显式指定开始爬取的url和解析的函数
# 默认response处理函数
def parse(self, response):
# 消息派发,对response存储完后再取其中有用的url进行增量爬取
self.process_data(response)
yield scrapy.Request(response.url, callback=self.parse)
def process_data(self, response):
pass
命令行启动
$ scrapy crawl myspider
目录层次
scrapy.cfg: 项目的配置文件
tutorial/: 该项目的python模块, 在这里添加代码
items.py: 项目中的item文件
pipelines.py: 项目中的pipelines文件.
settings.py: 项目全局设置文件.
spiders/ 爬虫模块目录
脚本启动
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from spiders.MySpider import MySpider
# 获取settings.py模块的设置
settings = get_project_settings()
process = CrawlerProcess(settings=settings)
# 指定要启动的爬虫
process.crawl(MySpider)
# 启动爬虫,会阻塞,直到爬取完成
process.start()
class MySpider(scrapy.Spider):
name #唯一名称
allowed_domains #指定域名范围
start_urls #指定开始爬取的链接
custom_settings #重写默认设置
crawler #指定爬取者,被from_crawler()指定,Crawler的实例
settings #指定设置,Settings的实例
logger #指定logger
#######################################################
from_crawler(crawler, *args, **kwargs) #指定Crawler实例,并向__init__传递参数
start_requests() #必须返回Requrst对象的迭代对象
make_requests_from_url(url) #接受url返回Request对象,默认回调parse
parse(response) #默认的回调,解析请求内容
log(message[, level, component]) #设置logger
closed(reason) #但爬虫被关闭时被调用
##########################
CrawlSpider # 可以定制规则
# 构造参数
class scrapy.http.Request(
url[,
callback, # 响应的函数
method='GET',
headers,
body,
cookies,
meta,
encoding='utf-8',
priority=0, # 优先级
dont_filter=False, # 请求完是否过滤,想多次请求指定True
errback] # 请求失败的回调函数
)
# 实例属性
request.url
request.method
request.headers
request.body
request.meta # 包含请求元数据的字典,当请求被复制时会被浅拷贝
request.copy() # 返回一个请求的拷贝
request.replace() # 返回一个替换过的请求,参数与初始化时相同
# 用于表单验证
class scrapy.http.FormRequest(url[, formdata, ...])
#返回一个填充好的FormRequest实例
scrapy.FormRequest.from_response(
response[, # 需要填充的表单
formname=None,
formnumber=0,
formdata=None, # 需要填充的数据
formxpath=None,
formcss=None,
clickdata=None,
dont_click=False,
...]
)
# 模拟登陆示例
class LoginSpider(scrapy.Spider):
name = 'example'
start_urls = ['http://www.example.com/users/login.php']
def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'john', 'password': 'secret'},
callback=self.after_login)
def after_login(self, response): #执行一些登陆后的操作
if "authentication failed" in response.body: #检查是否成功登陆
self.logger.error("Login failed")
# 构造方法
class scrapy.http.Response(url[, status=200, headers=None, body=b'', flags=None, request=None])
# 实例属性
response.url # 请求的url
response.status # 响应状态码
response.headers # 响应头,使用get()获得第一个,getlist()获得所有
response.body # 响应体
response.request # 响应的请求实例
response.meta # 相当与request.meta
response.flags # 响应的标记
response.copy() # 返回响应的拷贝
response.replace() # 返回替换过的响应对象
response.urljoin(url) # 构造一个绝对路径
class scrapy.http.TextResponse(url[, encoding[, ...]])
text_response.text # 响应体,作为unicode返回,同response.body.decode(response.encoding)
text_response.encoding # 指定的编码
text_response.selector # 选择器支持css和xpath
text_response.css() # 快捷方式
text_response.xpath() # 快捷方式
text_response.body_as_unicode() # 同text
selector相当于节点,通过选择再到子节点的集合,所有可以支持链式调用。
注意css和xpath的内部实现是相同的所以可以混合使用。
# 构造
class scrapy.selector.Selector(
response=None, # 从response中实例
text=None, # 同上
type=None
)
# 使用
# response实例的快捷方法
response.css() #返回SelectorList
response.xpath() #选择不到对象会返回None
response.css('img').xpath('@src') # 混合使用
html = '<html><body><span>good</span><span>buy</span></body></html>'
selector = Selector(text=html)
nodes = selector.xpath('//span')
for node in nodes:
node.extract() # 取出内容返回list
node.extract_first() # 取得list的第一个元素
用于结构化数据
使用
# item.py
from scrapy import Item, Field
class MyItem(Item):
title = Field()
link = Field()
desc = Field()
# spider.py
class MySpider(scrapy.Spider):
...
def parse(self, response):
for sel in response.xpath('//ul/li'):
item = MyItem()
item['title'] = sel.xpath('a/text()').extract()
item['link'] = sel.xpath('a/@href').extract()
tem['desc'] = sel.xpath('text()').extract()
print dmoz_item
使用ItemLoader加载数据
from scrapy.loader import ItemLoader
from myproject.items import MyItem
def parse(self, response):
loader = ItemLoader(item=MyItem, response=response)
loader.add_xpath('name', '//div[@class="xxx"]')
loader.add_css('stock', 'p#xxx]')
loader.add_value('last_updated', 'today') # you can also use literal values
return loader.load_item() # 填充item
定制ItemLoader
数据流动方向:
xpath或css解析 > input_processor > loader > output_processor > item
from scrapy.contrib.loader import ItemLoader
from scrapy.contrib.loader.processor import TakeFirst, MapCompose, Join
class ProductLoader(ItemLoader):
# 默认的输入/出处理器
default_input_processor = TakeFirst()
default_output_processor = TakeFirst()
# in为输入时调用的处理器,out同理
name_in = MapCompose(unicode.title)
name_out = Join()
上下文字典,loader声明,创建,调用时都能使用到一个上下文字段用于处理上下文相关数据
# 修改实例
loader = ItemLoader(product)
loader.context[‘unit’] = ‘cm’
# 实例化时传入
loader = ItemLoader(product, unit=’cm’)
# 处理器支持
class ProductLoader(ItemLoader):
length_out = MapCompose(parse_length, unit=’cm’)
内置处理器
class scrapy.loader.processors
模块
Identity
直接返回TakeFirst
返回第一个非空值Join(separator=' ')
返回分割符连接后的值Compose(*functions, **default_loader_context)
值流过多个函数后返回,相当于管道MapCompose(*functions, **default_loader_context)
类似上一个,但是输入值被迭代处理SelectJmes(json_path)
# 查询指定json路径并返回 数据深度处理
# 过滤重复的链接
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.links = set()
# process_item为固定的回调函数名
def process_item(self, item, spider):
if item['link'] in self.links:
# 跑出DropItem表示丢掉数据
raise DropItem("Duplicate item found: %s" % item)
else:
self.links.add(item['link'])
return item
# 保存数据
from Database import Database
class DatabasePipeline(object):
def __init__(self):
self.db = Database
def process_item(self, item, spider):
if self.db.item_exists(item['id']):
self.db.update_item(item)
else:
self.db.insert_item(item)
除process_item
外还有
open_spider
, spider_closed
配置
setting.py
全局配置
ITEM_PIPELINES = {
'pipelines.DuplicatesPipeline.DuplicatesPipeline': 1,
'pipelines.DatabasePipelin.DatabasePipelin': 2,
}
单独配置
class MySpider(Spider):
# 自定义配置
custom_settings = {
'ITEM_PIPELINES': {
'tutorial.pipelines.DatabasePipeline.DatabasePipelin': 1,
},
}
对连接规则的提取
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class LianjiaSpider(CrawlSpider):
name = "lianjia"
allowed_domains = ["lianjia.com"]
start_urls = [
"http://bj.lianjia.com/ershoufang/"
]
rules = [
# 匹配正则表达式,处理下一页
Rule(LinkExtractor(allow=(r'http://bj.lianjia.com/ershoufang/pg\s+$',)), callback='parse_item'),
# 匹配正则表达式,结果加到url列表中,设置请求预处理函数
# Rule(FangLinkExtractor(allow=('http://www.lianjia.com/client/', )), follow=True, process_request='add_cookie')
]
def parse_item(self, response):
pass
class scrapy.spiders.Rule(
link_extractor,
callback=None, # link_ex的回调函数
cb_kwargs=None, # 附加参数
follow=None, # 是否对请求完的链接应用当前的规则
process_links=None, # 处理所有连接的回调用于过滤
process_request=None # 链接请求预处理添加headers之类的
)
class scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(
allow=(), # 提取满足正则的连接
deny=(), # 排除满足正则的链接
allow_domains=(), #
deny_domains=(),
deny_extensions=None, # 排除的后缀名
restrict_xpaths=(), # 满足xpath的链接
restrict_css=(), # 同上
tags=('a', 'area'), # 提取指定标签下的连接
attrs=('href', ), # 提取拥有满足属性的连接
canonicalize=True, # 是否规范化
unique=True, # 链接去重
process_value=None # 值处理函数
)
import random
class RandomUserAgentMiddleware(object):
"""Randomly rotate user agents based on a list of predefined ones"""
def __init__(self, agents):
self.agents = agents
# 从crawler构造,USER_AGENTS定义在crawler的配置的设置中
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.getlist('USER_AGENTS'))
# 从settings构造,USER_AGENTS定义在settings.py中
@classmethod
def from_settings(cls, settings):
return cls(settings.getlist('USER_AGENTS'))
def process_request(self, request, spider):
# 设置随机的User-Agent
request.headers.setdefault('User-Agent', random.choice(self.agents))
DownloaderMiddleware
下载中间件
注意所有函数都是惰性的
class scrapy.downloadermiddlewares.DownloaderMiddleware
def process_request(request, spider):
yield Request # 返回请求,并向下传递
yield None # 忽略次中间件并传递到下一个中间件
yield Response # 直接中断,返回请求
raise IgnoreRequest # 调用self.process_exception(),无则调用Request.errback
def process_response(request, reponse, spider):
yield Request # 中断并传递请求到下载器
yield Response # 传递到下一个中间件
raise IgnoreRequest # 调用Request.errback() 无则忽略
def process_exception(request, exception, spider):
yield None
yield Response
yield Request
# setting.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
# None 指定关闭
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
}
内置的下载中间件
class scrapy.downloadermiddlewares.cookies.CookiesMiddleware
# setting.py
COOKIES_ENABLED
COOKIES_DEBUG
class scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware
# setting.py
DEFAULT_REQUEST_HEADERS
class scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware
# setting.py
DOWNLOAD_TIMEOUT
# 认证相关
class scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware
# 缓存相关
class scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware
单spider多cookies
# 通过meta字段传递
for i, url in enumerate(urls):
yield scrapy.Request("http://www.example.com", meta={'cookiejar': i},
callback=self.parse_page)
# 转发时需要重新指明
def parse_page(self, response):
# do some processing
return scrapy.Request("http://www.example.com/otherpage",
meta={'cookiejar': response.meta['cookiejar']},
callback=self.parse_other_page)
Cache
# setting.py
# 打开缓存
HTTPCACHE_ENABLED = True
# 设置缓存过期时间(单位:秒)
HTTPCACHE_EXPIRATION_SECS = 0
# 缓存路径(默认为:.scrapy/httpcache)
HTTPCACHE_DIR = 'httpcache'
# 忽略的状态码
HTTPCACHE_IGNORE_HTTP_CODES = []
# 缓存模式(文件缓存)
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
多线程
# 默认Item并发数:100
CONCURRENT_ITEMS = 100
# 默认Request并发数:16
CONCURRENT_REQUESTS = 16
# 默认每个域名的并发数:8
CONCURRENT_REQUESTS_PER_DOMAIN = 8
# 每个IP的最大并发数:0表示忽略
CONCURRENT_REQUESTS_PER_IP = 0
from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError
def errback(self, failure): #在Request初始化时指定的回调函数
if failure.check(HttpError): #检查错误类型
failure.value.response #响应的实例
failure.request #请求的实例
传递数据
def parse_page1(self, response):
item = MyItem()
item['main_url'] = response.url
request = scrapy.Request("http://www.example.com/some_page.html",
callback=self.parse_page2)
request.meta['item'] = item #使用meta传递了item
return request
def parse_page2(self, response):
item = response.meta['item']
item['other_url'] = response.url
return item
指定特殊键
键名 | 含义 |
---|---|
dont_redirect |
|
dont_retry |
|
handle_httpstatus_list |
|
handle_httpstatus_all |
|
dont_merge_cookies (see cookies parameter of Request constructor) |
|
cookiejar |
|
dont_cache |
|
redirect_urls |
|
bindaddress |
|
dont_obey_robotstxt |
|
download_timeout |
|
download_maxsize |
|
download_latency |
|
proxy |
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
引擎从调度器中取出一个链接(URL)用于接下来的抓取
引擎把URL封装成一个请求(Request)传给下载器
下载器把资源下载下来,并封装成应答包(Response)
爬虫解析Response
解析出实体(Item),则交给实体管道进行进一步的处理
解析出的是链接(URL),则把URL交给调度器等待抓取