zoukankan      html  css  js  c++  java
  • scrapy

    scrapy startproject tutorial

    scrapy crawl dmoz -o dmoz.json

    scrapy crawl dmoz -o dmoz.jl

    scrapy crawl dmoz -o dmoz.csv


    ----------------------------------------------------------------------

    scrapy shell "http://example.webscraping.com/places/default/view/Afghanistan-1"

    In [1]: response.css('title')
    Out[1]: [<Selector xpath=u'descendant-or-self::title' data=u'<title>Example web scraping website</tit'>]

    In [3]: response.css('title').extract()
    Out[3]: [u'<title>Example web scraping website</title>']

    In [5]: response.css('title').extract_first()
    Out[5]: u'<title>Example web scraping website</title>'

    In [8]: response.css('title::text').extract_first()
    Out[8]: u'Example web scraping website'

    In [11]: response.css('title::text').re(r'w+')
    Out[11]: [u'Example', u'web', u'scraping', u'website']

    In [18]: response.xpath('//title/text()').extract_first()
    Out[18]: u'Example web scraping website'
    -----------------------------------------------------------

    目前,为了加速页面的加载速度,页面的很多部分都是用JS生成的,而对于用scrapy爬虫来说就是一个很大的问题,因为scrapy没有JS engine,所以爬取的都是静态页面,对于JS生成的动态页面都无法获得。

    解决方案:

    • 利用第三方中间件来提供JS渲染服务: scrapy-splash 等。
    • 利用webkit或者基于webkit库

    Splash是一个Javascript渲染服务。它是一个实现了HTTP API的轻量级浏览器,Splash是用Python实现的,同时使用Twisted和QT。Twisted(QT)用来让服务具有异步处理能力,以发挥webkit的并发能力。


    下面就来讲一下如何使用scrapy-splash:

    1. 利用pip安装scrapy-splash库:
      $ pip install scrapy-splash
    2. scrapy-splash使用的是Splash HTTP API, 所以需要一个splash instance,一般采用docker运行splash,所以需要安装docker
    3. 安装docker, 安装好后运行docker。
    4. 拉取镜像(pull the image):
      $ docker pull scrapinghub/splash
    5. 用docker运行scrapinghub/splash:
      $ docker run -p 8050:8050 scrapinghub/splash
    6. 配置splash服务(以下操作全部在settings.py):

      1)添加splash服务器地址:

      SPLASH_URL = 'http://localhost:8050'  

      2)将splash middleware添加到DOWNLOADER_MIDDLEWARE中:

      DOWNLOADER_MIDDLEWARES = {
      'scrapy_splash.SplashCookiesMiddleware': 723,
      'scrapy_splash.SplashMiddleware': 725,
      'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
      }

      3)Enable SplashDeduplicateArgsMiddleware:

      SPIDER_MIDDLEWARES = {
      'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
      }

      4)Set a custom DUPEFILTER_CLASS:

      DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

      5)a custom cache storage backend:

      HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    7. 例子
      获取HTML内容:

    import scrapy
    from scrapy_splash import SplashRequest
    
    class MySpider(scrapy.Spider):
        start_urls = ["http://example.com", "http://example.com/foo"]
    
        def start_requests(self):
            for url in self.start_urls:
                yield SplashRequest(url, self.parse, args={'wait': 0.5})
    
        def parse(self, response):
            # response.body is a result of render.html call; it
            # contains HTML processed by a browser.
            # ...        
    --------------------------------------------------------------------

    背景

    Scrapy默认是可以保存为csv的,可以用excel打开,使用scrapy crawl spider_name -o data.csv即可。但csv格式有诸多不便,比如中文编码问题,比如说逗号分隔,虽然都能解决,但对一般用户来说还是不够友好。

    于是就想到了将数据直接保存进xlsx文件里,一劳永逸,可解决所有问题。主要用到了Scrapy的pipeline.py和python的开源库OpenPyxl.

    关于pipeline

    pipeline是scrapy中一个模块,数据被spider抓取之后会由pipeline处理。pipeline中通常会有几个“工序”,数据会按照顺序通过这几个“工序”。如果没有通过某项“工序”,会被抛弃掉。

    pipeline一般有几种用途:

    • 清洗HTML数据(比如清洗某无用tag)
    • 确认已抓取数据(比如确认是否包含特定字段)
    • 检查重复(过滤重复数据)
    • 保存已抓取数据入数据库

    我们在这里用到的是最后一个功能,只是保存为xlsx文件。

    关于OpenPyxl

    OpenPyxl是读写 Excel 2007 xlsx/xlsm文件的python库。废话不多说,直接上例子:

    from openpyxl import Workbook
    
    wb = Workbook()  # class实例化
    ws = wb.active  # 激活工作表
    
    ws['A1'] = 42  # A1表格输入数据
    ws.append(['科比', '1997年', '后卫', '赛季报销'])  # 添加一行数据
    
    wb.save('/home/alexkh/nba.xlsx')  # 保存文件
    

    Scrapy保存为excel

    Scrapy数据保存为excel就是在pipeline.py中处理。具体代码如下:

    #coding=utf-8
    from openpyxl import Workbook
    
    class TuniuPipeline(object):  # 设置工序一
        self.wb = Workbook()
        self.ws = self.wb.active
        self.ws.append(['新闻标题', '新闻链接', '来源网站', '发布时间', '相似新闻', '是否含有网站名'])  # 设置表头
    
    
    def process_item(self, item, spider):  # 工序具体内容
        line = [item['title'], item['link'], item['source'], item['pub_date'], item['similar'], item['in_title']]  # 把数据中每一项整理出来
        self.ws.append(line)  # 将数据以行的形式添加到xlsx中
        self.wb.save('/home/alexkh/tuniu.xlsx')  # 保存xlsx文件
        return item
    

    为了让pipeline.py生效,还需要在settings.py文件中增加设置,内容如下:

    ITEM_PIPELINES = {
        'tuniunews.pipelines.TuniuPipeline': 200,  # 200是为了设置工序顺序
    }
    
    --------------------------------------------------------------------

    1. Cookie原理


    HTTP是无状态的面向连接的协议, 为了保持连接状态, 引入了Cookie机制

    Cookie是http消息头中的一种属性,包括:

    • Cookie名字(Name)Cookie的值(Value)
    • Cookie的过期时间(Expires/Max-Age)
    • Cookie作用路径(Path)
    • Cookie所在域名(Domain),使用Cookie进行安全连接(Secure)。
      前两个参数是Cookie应用的必要条件,另外,还包括Cookie大小(Size,不同浏览器对Cookie个数及大小限制是有差异的)。

    更详细的cookie

    2. 模拟登陆


    这次主要爬取的网站是知乎

    爬取知乎就需要登陆的, 通过之前的python内建库, 可以很容易的实现表单提交

    现在就来看看如何通过Scrapy实现表单提交

    首先查看登陆时的表单结果, 依然像前面使用的技巧一样, 故意输错密码, 方面抓到登陆的网页头部和表单(我使用的Chrome自带的开发者工具中的Network功能)

    表单截图
    表单截图

    查看抓取到的表单可以发现有四个部分:

    • 邮箱和密码就是个人登陆的邮箱和密码
    • rememberme字段表示是否记住账号
    • 第一个字段是_xsrf,猜测是一种验证机制

    现在只有_xsrf不知道, 猜想这个验证字段肯定会实现在请求网页的时候发送过来, 那么我们查看当前网页的源码(鼠标右键然后查看网页源代码, 或者直接用快捷键)

    查询网页源码
    查询网页源码

    发现我们的猜测是正确的

    那么现在就可以来写表单登陆功能了

    def start_requests(self):
            return [Request("https://www.zhihu.com/login", callback = self.post_login)]  #重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数
    
        #FormRequeset
        def post_login(self, response):
            print 'Preparing login'
            #下面这句话用于抓取请求网页后返回网页中的_xsrf字段的文字, 用于成功提交表单
            xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
            print xsrf
            #FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单
            #登陆成功后, 会调用after_login回调函数
            return [FormRequest.from_response(response,   
                                formdata = {
                                '_xsrf': xsrf,
                                'email': '123456',
                                'password': '123456'
                                },
                                callback = self.after_login
                                )]
    

    其中主要的功能都在函数的注释中说明

    3. Cookie的保存


    为了能使用同一个状态持续的爬取网站, 就需要保存cookie, 使用cookie保存状态, Scrapy提供了cookie处理的中间件, 可以直接拿来使用

    CookiesMiddleware

    这个cookie中间件保存追踪web服务器发出的cookie, 并将这个cookie在接来下的请求的时候进行发送

    Scrapy官方的文档中给出了下面的代码范例 :

    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)
    

    那么可以对我们的爬虫类中方法进行修改, 使其追踪cookie

        #重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数
        def start_requests(self):
            return [Request("https://www.zhihu.com/login", meta = {'cookiejar' : 1}, callback = self.post_login)]  #添加了meta
    
        #FormRequeset出问题了
        def post_login(self, response):
            print 'Preparing login'
            #下面这句话用于抓取请求网页后返回网页中的_xsrf字段的文字, 用于成功提交表单
            xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
            print xsrf
            #FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单
            #登陆成功后, 会调用after_login回调函数
            return [FormRequest.from_response(response,   #"http://www.zhihu.com/login",
                                meta = {'cookiejar' : response.meta['cookiejar']}, #注意这里cookie的获取
                                headers = self.headers,
                                formdata = {
                                '_xsrf': xsrf,
                                'email': '123456',
                                'password': '123456'
                                },
                                callback = self.after_login,
                                dont_filter = True
                                )]
    

    4. 伪装头部


    有时候登陆网站需要进行头部伪装, 比如增加防盗链的头部, 还有模拟服务器登陆, 这些都在前面的爬虫知识中提到过

    Headers
    Headers

    为了保险, 我们可以在头部中填充更多的字段, 如下

        headers = {
        "Accept": "*/*",
        "Accept-Encoding": "gzip,deflate",
        "Accept-Language": "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4",
        "Connection": "keep-alive",
        "Content-Type":" application/x-www-form-urlencoded; charset=UTF-8",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
        "Referer": "http://www.zhihu.com/"
        }
    

    在scrapy中RequestFormRequest初始化的时候都有一个headers字段, 可以自定义头部, 这样我们可以添加headers字段

    形成最终版的登陆函数

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.selector import Selector
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.http import Request, FormRequest
    from zhihu.items import ZhihuItem
    
    
    
    class ZhihuSipder(CrawlSpider) :
        name = "zhihu"
        allowed_domains = ["www.zhihu.com"]
        start_urls = [
            "http://www.zhihu.com"
        ]
        rules = (
            Rule(SgmlLinkExtractor(allow = ('/question/d+#.*?', )), callback = 'parse_page', follow = True),
            Rule(SgmlLinkExtractor(allow = ('/question/d+', )), callback = 'parse_page', follow = True),
        )
        headers = {
        "Accept": "*/*",
        "Accept-Encoding": "gzip,deflate",
        "Accept-Language": "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4",
        "Connection": "keep-alive",
        "Content-Type":" application/x-www-form-urlencoded; charset=UTF-8",
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
        "Referer": "http://www.zhihu.com/"
        }
    
        #重写了爬虫类的方法, 实现了自定义请求, 运行成功后会调用callback回调函数
        def start_requests(self):
            return [Request("https://www.zhihu.com/login", meta = {'cookiejar' : 1}, callback = self.post_login)]
    
        #FormRequeset出问题了
        def post_login(self, response):
            print 'Preparing login'
            #下面这句话用于抓取请求网页后返回网页中的_xsrf字段的文字, 用于成功提交表单
            xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
            print xsrf
            #FormRequeset.from_response是Scrapy提供的一个函数, 用于post表单
            #登陆成功后, 会调用after_login回调函数
            return [FormRequest.from_response(response,   #"http://www.zhihu.com/login",
                                meta = {'cookiejar' : response.meta['cookiejar']},
                                headers = self.headers,  #注意此处的headers
                                formdata = {
                                '_xsrf': xsrf,
                                'email': '1095511864@qq.com',
                                'password': '123456'
                                },
                                callback = self.after_login,
                                dont_filter = True
                                )]
    
        def after_login(self, response) :
            for url in self.start_urls :
                yield self.make_requests_from_url(url)
    
        def parse_page(self, response):
            problem = Selector(response)
            item = ZhihuItem()
            item['url'] = response.url
            item['name'] = problem.xpath('//span[@class="name"]/text()').extract()
            print item['name']
            item['title'] = problem.xpath('//h2[@class="zm-item-title zm-editable-content"]/text()').extract()
            item['description'] = problem.xpath('//div[@class="zm-editable-content"]/text()').extract()
            item['answer']= problem.xpath('//div[@class=" zm-editable-content clearfix"]/text()').extract()
            return item
    

    5. Item类和抓取间隔


    完整的知乎爬虫代码链接

    from scrapy.item import Item, Field
    
    
    class ZhihuItem(Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        url = Field()  #保存抓取问题的url
        title = Field()  #抓取问题的标题
        description = Field()  #抓取问题的描述
        answer = Field()  #抓取问题的答案
        name = Field()  #个人用户的名称
    

    设置抓取间隔, 访问由于爬虫的过快抓取, 引发网站的发爬虫机制, 在setting.py中设置

    BOT_NAME = 'zhihu'
    
    SPIDER_MODULES = ['zhihu.spiders']
    NEWSPIDER_MODULE = 'zhihu.spiders'
    DOWNLOAD_DELAY = 0.25   #设置下载间隔为250ms
    

    更多设置可以查看官方文档

    抓取结果(只是截取了其中很少一部分)

    ...
     'url': 'http://www.zhihu.com/question/20688855/answer/16577390'}
    2014-12-19 23:24:15+0800 [zhihu] DEBUG: Crawled (200) <GET http://www.zhihu.com/question/20688855/answer/15861368> (referer: http://www.zhihu.com/question/20688855/answer/19231794)
    []
    2014-12-19 23:24:15+0800 [zhihu] DEBUG: Scraped from <200 http://www.zhihu.com/question/20688855/answer/15861368>
        {'answer': [u'u9009u4f1au8ba1u8fd9u4e2au4e13u4e1auff0cu8003CPAuff0cu5165u8d22u52a1u8fd9u4e2au884cu5f53u3002u8fd9u4e00u8defu8d70u4e0bu6765uff0cu6211u53efu4ee5u5f88u80afu5b9au7684u544au8bc9u4f60uff0cu6211u662fu771fu7684u559cu6b22u8d22u52a1uff0cu70edu7231u8fd9u4e2au884cu4e1auff0cu56e0u6b64u575au5b9au4e0du79fbu5730u5728u8fd9u4e2au884cu4e1au4e2du8d70u4e0bu53bbu3002',
                    u'u4e0du8fc7u4f60u8bf4u6709u4ebau4eceu5c0fu5c31u559cu6b22u8d22u52a1u5417uff1fu6211u89c9u5f97u51e0u4e4eu6ca1u6709u5427u3002u8d22u52a1u7684u9b45u529bu5728u4e8eu4f60u771fu6b63u61c2u5f97u5b83u4e4bu540eu3002',
                    u'u901au8fc7u5b83uff0cu4f60u53efu4ee5u5b66u4e60u4efbu4f55u4e00u79cdu5546u4e1au7684u7ecfu8425u8fc7u7a0buff0cu4e86u89e3u5176u7eb7u7e41u5916u8868u4e0bu7684u5b9eu7269u6d41u3001u73b0u91d1u6d41uff0cu751au81f3u4f60u53efu4ee5u638cu63e1u5982u4f55u53bbu7ecfu8425u8fd9u79cdu5546u4e1au3002',
                    u'u5982u679cu5bf9u4f1au8ba1u7684u8ba4u8bc6u4ec5u4ec5u505cu7559u5728u505au5206u5f55u8fd9u4e2au5c42u9762uff0cu5f53u7136u4f1au89c9u5f97u67afu71e5u65e0u5473u3002u5f53u4f60u5bf9u5b83u7684u8ba4u8bc6u8fdbu5165u5230u6df1u5c42u6b21u7684u65f6u5019uff0cu4f60u81eau7136u5c31u4f1au559cu6b22u4e0au5b83u4e86u3002
    
    
    '],
         'description': [u'u672cu4ebau5b66u4f1au8ba1u6559u80b2u4e13u4e1auff0cu6df1u611fu5176u67afu71e5u4e4fu5473u3002
    u5f53u521du662fu51b2u7740u5e08u8303u4e13u4e1au62a5u7684uff0cu56e0u4e3au68a6u60f3u662fu6210u4e3au4e00u540du8001u5e08uff0cu4f46u662fu611fu89c9u73b0u5728u666eu901au521du9ad8u4e2du8001u5e08u5df2u7ecfu8d8bu4e8eu9971u548cuff0cu800cu987au6bcdu4eb2u5927u4ebau7684u610fu9009u4e86u8fd9u4e2au4e13u4e1au3002u6211u559cu6b22u4e0au6559u80b2u5b66u7684u8bfeuff0cu5e76u597du7814u7a76u5404u79cdu6559u80b2u5fc3u7406u5b66u3002u4f46u4f1au8ba1u8bfeu4f3cu4e4eu662fu4e3bu6d41u3001u54ceu3002
    
    u4e00u76f4u4e0du559cu6b22u94b1u4e0du94b1u7684u4e13u4e1auff0cu6240u4ee5u5f88u597du5947u5927u5bb6u9009u4f1au8ba1u4e13u4e1au5230u5e95u662fu51fau4e8eu4ec0u4e48u76eeu7684u3002
    
    u6bd4u5982u8bf4u5b66u4e2du6587u7684u4f1au8bf4u4eceu5c0fu559cu6b22u770bu4e66uff0cu4f1au6709u4eceu5c0fu559cu6b22u4f1au8ba1u501fu554au8d37u554au7684u7684u4ebau5417uff1f'],
         'name': [],
         'title': [u'
    
    ', u'
    
    '],
         'url': 'http://www.zhihu.com/question/20688855/answer/15861368'}
    ...
    

    6. 存在问题

    • Rule设计不能实现全网站抓取, 只是设置了简单的问题的抓取
    • Xpath设置不严谨, 需要重新思考
    • Unicode编码应该转换成UTF-8


    作者:Andrew_liu
    链接:https://www.jianshu.com/p/b7f41df6202d
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    -------------------------------------------------------------

    关于解决scrapy爬虫函数之间传值的问题

    有的时候我们爬取数据的时候需要在多个页面之间跳转,爬取完所有页面的数据的时候才能把所有数据一起存到数据库,这个时候我们就需要把某个函数内爬取的数据传到下一个函数当中。
    有人可能会说,为什么不用全局变量呢?这是因为scrapy自带多线程机制,好几个线程同时跑,用全局变量很不明智,除非顺序逻辑非常清楚的程序。
    这个时候我们就要用到scrapy中Request方法的meta参数

    def first(self, response)
    code = response.xpath('*****').extract()[0]
    yield FormRequest(url=url,
    meta={
        'code':code
    }, 
    formdata={},
    callback=self.next)
    

    meta和formdata属性一样,是一个字典,这里面我们把从网页中爬取到的code值放在meta中传入下一个函数

    def next(self, response)
    code = response.meta["code"]
    

    在下一个函数当中我们就可以从response对象当中取得meta中的code值,这里就可以确保两个函数中code的值是完全一致的。






  • 相关阅读:
    angular 按下回车键触发事件
    vue 父组件与子组件的通信
    最近在开发一个文章聚合的工具
    Martinjingyu的开发环境
    个推push数据统计(爬虫)
    基于redis的订单号生成方案
    电商平台--Mysql主从搭建(2)
    Mysql主从搭建(1)
    mysql物理级别热备脚本
    外键查询及删除
  • 原文地址:https://www.cnblogs.com/mhc-fly/p/8317854.html
Copyright © 2011-2022 走看看