Scrapy

2020-03-18  本文已影响0人  奇而思

基本用法

  1. python -m scrapy startproject yourproject 建立project
  2. 在project下的spider文件夹下,新建文件定义一个类,这个类要继承自scrapy.Spider。必须具有属性name,这个属性的值在整个project中不能重复。
    其他属性值可以在命令行中通过-a property_name=value的形式动态传入。
  3. start_urls属性,定义起始url。(也可以使用函数start_requests)
  4. parse函数(默认的回调函数),在该函数中对页面进行解析,并产生新的请求。这些新的请求由scrapy统一调度和去重。在parse函数中,yield新请求可以使用其他回调函数。结果可以通过dict形式yield输出。
  5. 运行
    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()内部定义。

优先级为:

  1. ItemLoader中定义的field_in,field_out
  2. Item的Field定义的
  3. 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

  1. 可以通过继承扩展processor,便于维护。例子参考官网
  2. output processor一般在field中定义。因为它一般取决于Field的内容,而不是和网站的parsing规则有关

内置的processor

  1. scrapy.loader.processors.Identity: 原样返回输入内容,不接受loader_context参数
  2. TakeFirst:返回第一个非空值,对于单值的field,这个一般用于output processor,不接受loader_context
  3. Join(separator=u' '): 返回空格连接的数据,不接受loader_context
  4. Compose(*functions, **default_loader_context): 输入数据依次经过functions的处理,最终返回最后一个函数的结果。默认当返回None时退出,可以通过关键字参数stop_on_none=False改变。每个函数可以接受loader_context参数,如果有则会将当前的context传入。
  5. MapCompose(*functions, **default_loader_context)
    与Compose的区别在于参数传递的方式
    这个processor的输入被迭代,每个元素传递给第一个函数,得到的结果组成一个新的iteriable,然后依次传递给第二个函数,结果再组成iterable。如果返回的是list,会被展平,如果返回none会被忽略。最终,最后一个函数的结果组成iterable返回。
    这个一般用于input_processor,因为抽取数据使用的extract()一般返回一个列表。每个函数可以接受loader_context参数,如果有则会将当前的context传入。
  6. SelectJmes(json_path): 查询__init__方法中指定的json并返回结果,需要依赖 jmespath (https://github.com/jmespath/jmespath.py)

pipeline

对获取的item进行处理,典型的作用

  1. 清理html数据
  2. 验证获取的数据(是否包含某些特定的field)
  3. 检查重复数据
  4. 存储数据到数据库中

pipeline也是python的类,必须包含以下方法

  1. process_item(self,item,spider)
    这个函数要么返回dict, Item,要么返回Deferred,或者raise DropItem exception
  2. open_spider(self,spider): 当spider开启时调用
  3. close_spider(self,spider)
  4. 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的方式来运行。

上一篇下一篇

猜你喜欢

热点阅读