Scrapy

基本用法

  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的方式来运行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容