Scrapy

1.Scrapy的命令行命令
  • 创建一个Scrapy工程
    终端输入:
scrapy startproject <project_name> [project_dir]
//在project_dir下创建一个Scrapy项目,如果project_dir省略的话,
//会创建一个和<project_name>同名的文件夹内,项目放在当前的文件路径下
  • PyCharm 下直接运行 Scrapy
    Scrapy提供了一个cmdline模块,使Python的模块直接可以运行命令行
    方法如下:
    在目录的顶层(非必须,主要是为了和业务代码分开),单独创建main.py文件.直接运行该文件, exampleName替换为你的Spider的名字 ,即 你的spider的 name属性的内容

main.py文件内容如下

 # -*- coding=utf8 -*-from scrapy import 
from scrapy import cmdline
cmdline.execute("scrapy crawl exampleName".split())
2.目录分类:
* 模拟登陆
* XPath和CSS的选择器的使用
* Scrapy整体运行流程
* Python的setter和getter方法的创建
* Scrapy如何管理Request队列
* item pipeline的使用
* Request
* 验证码识别
2.1 模拟登陆
1.如何快速定位POST的数据格式
    ```
    1.使用抓包工具进行解析
   ```
2.表单数据的Scrapy的数据提交和回调
def start_requests(self):   
    """    登陆页面 获取xrsf    """   
   return [Request(    "https://www.zhihu.com/#signin",  \
         meta={'cookiejar': 1},\
         callback=self.post_login\
       )]
//Scrapy 通过start_requests方法返回Request的请求list来进行请求,
//通过callback参数来确定回调方法,默认的回调方法是 parser.
//如果不重写start_requests方法,那就必须要提供start_urls 类的变量,
//官方说法是start_urls如果为空才调用start_requests方法生成请Request
//但是我测试的时候是start_requests会覆盖掉start_urls的请求列表

不同回调方法官方示例:

import scrapy
class AuthorSpider(scrapy.Spider):
          name = 'author'
          start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
        # follow links to author pages
        for href in response.css('.author+a::attr(href)').extract():
               yield scrapy.Request(response.urljoin(href),
                                     callback=self.parse_author)
         # follow pagination links
         next_page = response.css('li.next a::attr(href)').extract_first()
          if next_page is not None:
               next_page = response.urljoin(next_page)
                yield scrapy.Request(next_page, callback=self.parse)
def parse_author(self, response):
       def extract_with_css(query):
              return response.css(query).extract_first().strip()
        yield {
          'name': extract_with_css('h3.author-title::text'),
          'birthdate': extract_with_css('.author-born-date::text'),
          'bio': extract_with_css('.author-description::text'),
                }
2.2XPath和CSS的选择器的使用
1.XPath和CSS的语法
  详情看专门章节
2.XPath和CSS选择器

scrapy官方建议采用XPath选择器,因为CSS选择器的底层也是采用的XPath实现的.

 response.css("xxx") 
// 该方法返回一个类似list的SelectorList对象.
 response.css('title::text').extract()
//直接获取text便签下的内容
 response.css('title::text').extract_first()
和response.css('title::text')[0].extract() 是相同功能.但extract_first(),在访问的对象不存在是返回None,但后者却是直接抛出异常.
  response.css('title::text').re(r'Quotes.*') 
  response.css('title::text').re(r'(\w+) to (\w+)')
 // re( )方法可以通过正则表达式,获取需要的对象.

CSS和XPath可以相互转换

from scrapy import Selector
sel = Selector(text='<div class="hero shout"><div class="mmm">dffdf</div><time datetime="2014-07-23 19:00">Special date</time></div>')
print(sel.css('.shout').xpath('./time/@datetime').extract())
print(sel.xpath('.//div')[1].css('.mmm').extract())

输出:

['2014-07-23 19:00']
['<div class="mmm">dffdf</div>']
2.3item pipeline的使用

1 . item
item 类提供了一个类似dict的API ,用来提取/存储数据.也就是说你可以把它当做dict来用.
声明方法:

import scrapy
class Product(scrapy.Item):
            name = scrapy.Field()
            price = scrapy.Field()
            stock = scrapy.Field()
            last_updated = scrapy.Field(serializer=str)
//Field()简单理解为初始化方法

创建方法:

product = Product(name='Desktop PC', price=1000)
print product
#Product(name='Desktop PC', price=1000)

值获取:

>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC
>>> product['price']
1000
>>> product['last_updated']Traceback (most recent call last):
...
KeyError: 'last_updated'>>> product.get('last_updated', 'not set')
not set
>>> product['lala'] # getting unknown fieldTraceback (most recent call last):
...
KeyError: 'lala'
>>> product.get('lala', 'unknown field')
'unknown field'
>>> 'name' in product # is name field populated?
True
>>> 'last_updated' in product # is last_updated populated?
False
>>> 'last_updated' in product.fields # is last_updated a declared field?
True

快速赋值:

>>> product2 = Product
(product)
>>> print product2Product
(name='Desktop PC', price=1000)
>>> product3 = product2.copy()
>>> print product3Product
(name='Desktop PC', price=1000)

ItemLoaders 提供解析是快速赋值,有时间再看

2 . Item Pipeline

Pipeline类默认方法

 def process_item(self, item, spider)
#这个方法是每一个parse函数(callback)都会调用(必须实现方法)
返回值是后面之中之一:包含数据的字典,Item的类,raise DropItem exception
 def open_spider(self, spider)
#爬虫开始执行时调用
def close_spider(self, spider)
#爬虫结束时调用
def from_crawler(cls, crawler)
#生成这个单例类用的配置方法,必须返回一个 Crawler 实例

激活 Item Pipeline 模块
在设置模块中添加如下代码:

ITEM_PIPELINES = { 'myproject.pipelines.PricePipeline': 300, 
'myproject.pipelines.JsonWriterPipeline': 800,
}
#后面的 300, 800 是调用的优先级,范围: 0 ~ 1000
* Scrapy如何管理Request队列

这有一点未弄清楚,就是分析出的URLs再次加入爬取队列的逻辑.

engine会判断返回的数据类型(item或者是Requst)分别交给item pipline或者是Scheduler将要请求的列表 详细看 4.Scrapy整体运行流程

3 . Request

只写提交表单的FormRequest

import scrapy
class LoginSpider(scrapy.Spider):
          name = 'example.com'
          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")
                return
         # continue scraping with authenticated session...
4. Scrapy整体运行流程

数据流向图:

Data_flow.png

在Scrapy中,数据流被执行Engine控制着,运行方式如下:

  1. Engine 从Spider获取初始化的Requests.
  2. Engine 把Requests传递给Scheduler ,并询问下一个需要爬取得请求 Requests.
  3. Scheduler 返回下一个Requests 给 Engine
  4. Engine 把3中获得的Requests通过中间件 (Downloader Middlewares先调用process_request()函数)发送给Downloader.
  5. 当页面下载完成,Dowenloader会生成一个Response,并把这个Response通过中间件(Downloader Middlewares先调用process_response()函数)发送给 Engine.
  6. Engine 获得Dowenloader 生成的Response,然后通过中间件(Spider Middleware先调用process_spider_input()函数)发送给Spider
  7. Spider 处理这个Response,然后通过中间件(Spider Middleware先调用process_spider_output()函数)返回抽取生成的items和Reqeusts(网页中需要爬取得连接生成的请求)
  8. Engine 发送处理过的items给Item Piplines,然后发送 7中生成的请求到Scheduler并询问下一个需要爬取的请求.
  9. 重复步骤1 , 直到Scheduler中没有请求
4.1 中间件

Downloader Middlewares 和Spider Middleware
中间件其实就是个Python的class文件,实现对应的方法并在配置文件中声明注册即可

 Downloader Middlewares 中间件 : 
process_request() :如果要在爬取请求没有开始前对请求进行处理.可选这个函数
process_response():页面返回的数据统需要处理,可以选着个函数.

下载中间件使用场景:

  • 需要在请求发送到Downloader之前需要处理请求
  • 需要在相应被发送给spider之前改变的响应的内容
  • 静默的丢弃一些请求
Spider Middleware 中间件:
process_spider_input() :如果需要在Spider没有处理Response强处理响应的话,可以调用这个函数
process_spider_output():如果需要在Spider的parse的函数解析后处理数据,可以调用这个函数

Spider Middleware 中间件使用场景:

  • 处理spider的callback的返回内容 -改变/添加/移除items或Requests
  • 处理开始请求
  • 处理spider的异常
  • call errback instead of callback for some of the requests based on response content.
激活下载中间件

在配置文件中注册:

DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543,
}

Scrapy 中有内建的中间件如下

{
 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddlewar': 300,
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
 'scrapy.downloadermiddlewares.chunked.ChunkedTransferMiddleware': 830,
 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}
后面的数字是调用顺序.你注册的中间件会按照数值合并到系统默认的表中(也就是上边的表).

自定义下载中间件需要实现下面的一个或多个方法:

process_request(request, spider)
每一个经过下载中心的请求都会调用这个函数:返回值 可能是None, Response, Request, 或者IgnoreRequest.

* 返回 None: Scrapy会继续处理这个请求,知道请求结束
* 返回 Response ,Scrapy 不会调用 其他的process_request() or process_exception() 以及下载方法.直接返回 这个Response. 但是  process_response() 什么时候都会被调用
* 返回 Request ,Scrapy 将会停止调用 process_request 方法 并重新安排这个返回的请求.(这个方法未知)
* 返回 IgnoreRequest,  process_exception() 方法会被调用.如果没有被调用,err back方法被调用. 仍然没有,就会被忽略
process_response(request, response, spider)
 返回一个Response 对象,或一个Request 对象,或者抛出一个IgnoreRequest 异常.
返回 Response: 将会被下一个中间件链中的 process_response() 处理.
返回 Request : 中间件链将会终止,这个请求会被重新安排到下载中心.这个行为类似process_request()的返回Request.
返回 IgnoreRequest: Request.errback 函数会被调用,如果没有处理这个异常,这个异常将会被忽略并且不会有log输出(不同于其他的异常).
激活请求中间件

方法和下载中间件类似,在配置文件中注册

SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543,
}
process_spider_input(response, spider)
每一个请求响应在返回Spider被处理之前调用这个方法. 方法返回 None 或抛出一个异常.
返回 None : Scrapy 继续执行余下流程
抛出一个异常: Scrapy 将终止调用其他的 spider 中间件的 process_spider_input
方法,并调用 request errback函数. 这个err back 函数返回的结果会被
process_spider_output() 函数处理,如果err back也抛出一个异常.
,则process_spider_exception()函数会被调用.

process_spider_output(response, result, spider)
这个方法会在response被spider处理后调用.
process_spider_output() 必须返回一个Request, dict or Item objects

process_spider_exception(response, exception, spider)
当 一个spider 或者process_spider_input()(来自其他spider的中间件)  方法抛出一个异常
process_spider_exception() 应该返回一个 None 或一个可迭代的 Response, dict 或Item objects.

返回 None :Scrapy 继续处理这个异常.执行余下流程中的其他的中间件process_spider_exception() 函数,直到没有中间件.最终这个异常会到达engine(将会有log)
返回一个可迭代的对象:  异常链终止

Spider Middleware 内建中间件

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

推荐阅读更多精彩内容