基本用法
-
python -m scrapy startproject yourproject
建立project - 在project下的spider文件夹下,新建文件定义一个类,这个类要继承自scrapy.Spider。必须具有属性
name
,这个属性的值在整个project中不能重复。
其他属性值可以在命令行中通过-a property_name=value
的形式动态传入。 - start_urls属性,定义起始url。(也可以使用函数start_requests)
- parse函数(默认的回调函数),在该函数中对页面进行解析,并产生新的请求。这些新的请求由scrapy统一调度和去重。在parse函数中,yield新请求可以使用其他回调函数。结果可以通过dict形式yield输出。
- 运行
scrapy crawl quotes -o quotes-humor.json -a tag=humor
scrapy每次运行都会在旧文件上进行append,因此,对于json文件可能存在文件结构破坏的问题。可以使用ql
Scrapy 更推荐使用XPath来解析
使用scrapy shell来调试
python -m scrapy shell "http://quotes.toscrape.com/page/1/"
注意在windows中使用双引号
Items
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
tags = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
items用于存放数据。
通过实例的fields属性可以得到所有Field。访问和字典操作一致。
深拷贝:product2 = product.deepcopy()
CrawlSpider
一个基于Spider但是功能丰富一点的spider,可以基于这个开始写你自己的spider。
可以定义Rule和对应的callback函数。(注意,这里不要使用parse作为回调函数)
ItemLoader
Items提供存储数据的容器,ItemLoader提供批量产出的机制,便于维护
使用
在spider的parse回调函数中使用
示例
def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath('name', '//div[@class="product_name"]')
l.add_xpath('name', '//div[@class="product_title"]')
l.add_xpath('price', '//p[@id="price"]')
l.add_css('stock', 'p#stock]')
l.add_value('last_updated', 'today') # you can also use literal values
return l.load_item()
过程:
在ItemLoader中,每一个field都有一个input processor和output processor。input processor负责收集信息并保存在ItemLoader内部,调用load_item()
函数后,由output processor产出item。
如果需要在一个页面获取多个item,那么可以通过在Loader初始化时传入selector的形式。看例子:
import scrapy
from itemloader.items import quoteItem
from itemloader.loaders import quoteLoader
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['127.0.0.1:8080']
start_urls = ['http://127.0.0.1:8080/Quotes%20to%20Scrape.htm/']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
loader = quoteLoader(item=quoteItem(), selector=quote)
loader.add_css('text', '.text::text')
loader.add_css('author', '.author::text')
loader.add_css('tags', '.tag::text')
yield loader.load_item()
声明
通过继承ItemLoader来声明你自己的Itemloader类。每一个field有对应的_in
, _out
后缀的变量指明对应的processor,也可以通过default_input_processor
default_output_processor
指定默认的processor。
当然也可以在Item定义时,在Field()内部定义。
优先级为:
- ItemLoader中定义的field_in,field_out
- Item的Field定义的
- default_input_processor
上下文:
通过在函数中增加loader_context
变量,可以使用ItemLoader内部的上下文
这个上下文一般就是指初始化__init__()
时传入的关键字参数或内部的一些属性设置。
嵌套的Loader
对于一些比较长的XPath,可以通过嵌套的方式简化代码或便于调试
例如
loader = ItemLoader(item=Item())
# load stuff not in the footer
footer_loader = loader.nested_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class = "social"]/@href')
footer_loader.add_xpath('email', 'a[@class = "email"]/@href')
# no need to call footer_loader.load_item()
loader.load_item()
processors
- 可以通过继承扩展processor,便于维护。例子参考官网
- output processor一般在field中定义。因为它一般取决于Field的内容,而不是和网站的parsing规则有关
内置的processor
- scrapy.loader.processors.Identity: 原样返回输入内容,不接受loader_context参数
- TakeFirst:返回第一个非空值,对于单值的field,这个一般用于output processor,不接受loader_context
- Join(separator=u' '): 返回空格连接的数据,不接受loader_context
- Compose(*functions, **default_loader_context): 输入数据依次经过functions的处理,最终返回最后一个函数的结果。默认当返回None时退出,可以通过关键字参数
stop_on_none=False
改变。每个函数可以接受loader_context
参数,如果有则会将当前的context传入。 - MapCompose(*functions, **default_loader_context)
与Compose的区别在于参数传递的方式
这个processor的输入被迭代,每个元素传递给第一个函数,得到的结果组成一个新的iteriable,然后依次传递给第二个函数,结果再组成iterable。如果返回的是list,会被展平,如果返回none会被忽略。最终,最后一个函数的结果组成iterable返回。
这个一般用于input_processor,因为抽取数据使用的extract()
一般返回一个列表。每个函数可以接受loader_context
参数,如果有则会将当前的context传入。 - SelectJmes(json_path): 查询
__init__
方法中指定的json并返回结果,需要依赖 jmespath (https://github.com/jmespath/jmespath.py)
pipeline
对获取的item进行处理,典型的作用
- 清理html数据
- 验证获取的数据(是否包含某些特定的field)
- 检查重复数据
- 存储数据到数据库中
pipeline也是python的类,必须包含以下方法
- process_item(self,item,spider)
这个函数要么返回dict, Item,要么返回Deferred,或者raise DropItem exception - open_spider(self,spider): 当spider开启时调用
- close_spider(self,spider)
- from_crawler(cls,crawler)
如果这个函数存在,则这个类方法会在Crawler中调用以创建一个pipeline,必须返回一个pipeline实例。
Crawler对象能够访问所有的Scrapy核心components,例如setting, signals。这是pipeline访问这些部件的一个方法。
激活pipeline
要使用pipeline,必须将对应的类放进setting中的ITEM_PIPELINES,
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
后面的数字为优先级,从0-1000的数字,先经过数值低的pipeline
selector
推荐使用xpath。通过response.xpath("//div")
的形式会返回一个selector对象。如果要从中取值,推荐使用get()
getall()
函数(2.0)替代之前的extract(), extract_first()
函数。
因为,get()
函数总是返回第一个值,且为单值。getall()
函数总是返回列表,即使只有一个值。这两个函数的结果更加通用。
xpath中的相对路径
如果使用嵌套selector,那么xpath如果以/
开头,那么会认为是绝对路径。
看例子,例如你想抽取文档中<div>
下的所有<p>
,你先写了:
divs = response.xpath('//div')
那么下面的语句是错误的,会找到所有的p
for p in divs.xpath("//p"):
print(p.get())
应该写为:
>>> for p in divs.xpath('.//p'): # extracts all <p> inside
... print(p.get())
或者
>>> for p in divs.xpath('p'):
... print(p.get())
查询class时考虑使用CSS
CSS查询类更简洁。selector可以连接,因此可以用css查询类,后面再用xpath查询
小区别
注意//node[1]
和(//node)[1]
的区别
//node[1]
选择每个在相应父节点下的node的第一个
(//node)[1]
选择所有node的第一个
XPath中使用变量
response.xpath('//div[count(a)=$cnt]/@id', cnt=5).get()
问题
1. scrapy创建项目时报错:Fatal error in launcher: Unable to create process using 。。。
这个问题是python的安装位置问题。解决方案:
运行python -m scrapy startproject yourproject
的方式来运行。
网友评论