• scrapy框架的使用


    scrapy简介

    Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下

    Scrapy主要包括了以下组件:

    • 引擎(Scrapy)
      用来处理整个系统的数据流处理, 触发事务(框架核心)
    • 调度器(Scheduler)
      用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
    • 下载器(Downloader)
      用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
    • 爬虫(Spiders)
      爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
    • 项目管道(Pipeline)
      负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
    • 下载器中间件(Downloader Middlewares)
      位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
    • 爬虫中间件(Spider Middlewares)
      介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
    • 调度中间件(Scheduler Middewares)
      介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

    Scrapy运行流程大概如下:

    1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
    2. 引擎把URL封装成一个请求(Request)传给下载器
    3. 下载器把资源下载下来,并封装成应答包(Response)
    4. 爬虫解析Response
    5. 解析出实体(Item),则交给实体管道进行进一步的处理
    6. 解析出的是链接(URL),则把URL交给调度器等待抓取

    安装

    Linux下的安装(包括mac)

      pip install scrapy

    Windows下的安装

      a. 下载twisted 
        http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
      b. 安装wheel
        pip3 install wheel
      c. 安装twisted
        进入下载目录,执行 pip3 install Twisted‑18.7.0‑cp36‑cp36m‑win_amd64.whl
      d. 安装pywin32
        pip3 install pywin32
      e. 安装scrapy
        pip3 install scrapy
     

    基本命令

    1. scrapy startproject 项目名称
       - 在当前目录中创建一个项目文件(类似于Django)
     
    2. scrapy genspider [-t template] <name> <domain>
       - 创建爬虫应用
       如:
          scrapy gensipider -t basic oldboy oldboy.com
          scrapy gensipider -t xmlfeed autohome autohome.com.cn
         或者简单直接   scrapy gensipider app名  要爬取的域名
       PS:
          查看所有命令:scrapy gensipider -l
          查看模板命令:scrapy gensipider -d 模板名称
     
    3. scrapy list
       - 展示爬虫应用列表
     
    4. scrapy crawl 爬虫应用名称
       - 运行单独爬虫应用

    备注:scrapy crawl 应用名称  表示以日志的形式运行爬虫应用,可以在后面加 --nolog  取消日志    scrapy crawl 名称  --nolog

    项目结构以及爬虫应用简介

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    project_name/
       scrapy.cfg
       project_name/
           __init__.py
           items.py
           middlewares.py
     
           pipelines.py
           settings.py
           spiders/
               __init__.py
               爬虫1.py
               爬虫2.py
               爬虫3.py

    文件说明:

    • scrapy.cfg  项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
    • items.py    设置数据存储模板,用于结构化数据,如:Django的Model
    • pipelines    数据处理行为,如:一般结构化的数据持久化
    • settings.py 配置文件,如:递归的层数、并发数,延迟下载等
    • spiders      爬虫目录,如:创建文件,编写爬虫规则

    注意:一般创建爬虫文件时,以网站域名命名

    windows系统编码错误时:
    解决方法:
    import sys,io
    sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    import scrapy
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
    
        def parse(self, response):
            pass
    爬虫.py
     
    备注:可以在settings.py中配置user-agent

    在爬取数据时,可以选择是否往.../robots.txt/发送验证,是否允许爬取,一般设置为False

    使用scrapy解析文本内容时,可以使用每个应用中的response.xpath(xxx) 进行数据的解析。

    print(response.xpath(...))  得到的是一个Selector对象。selector对象可以继续xpath进行数据的解析。
    备注:xpath使用方法:
      1.//+标签  表示从全局的子子孙孙中查找标签    
      2./+标签   表示从子代中查找标签
      3.查找带有xxx属性的标签:   标签+[@标签属性="值"]   
      4.查找标签的某个属性:  /标签/@属性  
      5.从当前标签中查找时:.//+标签      
    response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
    hxs = HtmlXPathSelector(response)
    print(hxs)   # selector对象
    hxs = Selector(response=response).xpath('//a')
    print(hxs)    #查找所有的a标签
    hxs = Selector(response=response).xpath('//a[2]')
    print(hxs)    #查找某一个具体的a标签    取第三个a标签
    hxs = Selector(response=response).xpath('//a[@id]')
    print(hxs)    #查找所有含有id属性的a标签
    hxs = Selector(response=response).xpath('//a[@id="i1"]')
    print(hxs)    # 查找含有id=“i1”的a标签
    # hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]')
    # print(hxs)   # 查找含有href=‘xxx’并且id=‘xxx’的a标签
    # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')
    # print(hxs)   # 查找 href属性值中包含有‘link’的a标签
    # hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]')
    # print(hxs)   # 查找 href属性值以‘link’开始的a标签
    # hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]')
    # print(hxs)   # 正则匹配的用法   匹配id属性的值为数字的a标签
    # hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]/text()').extract()
    # print(hxs)    # 匹配id属性的值为数字的a标签的文本内容
    # hxs = Selector(response=response).xpath('//a[re:test(@id, "id+")]/@href').extract()
    # print(hxs)    #匹配id属性的值为数字的a标签的href属性值
    # hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract()
    # print(hxs)
    # hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first()
    # print(hxs)
     
    # ul_list = Selector(response=response).xpath('//body/ul/li')
    # for item in ul_list:
    #     v = item.xpath('./a/span')
    #     # 或
    #     # v = item.xpath('a/span')
    #     # 或
    #     # v = item.xpath('*/a/span')
    #     print(v)
    备注:xpath中支持正则的使用:    用法  标签+[re:test(@属性值,"正则表达式")]
      获取标签的文本内容:   /text()     
      获取第一个值需要  selector_obj.extract_first()    获取所有的值  selector_obj.extract()  值在一个list中

    scrapy的持久化

    scrapy的持久化过程分为四个部分

      首先,items定义传输的格式,其次,在爬虫应用中yield这个item对象,pipeline收到yield的item对象,进行持久化操作,这个过程中,settings中要进行相应的配置

    items.py

    # 规范持久化的格式
    import scrapy
    
    
    class MyspiderItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        url=scrapy.Field()

    爬虫应用

    import scrapy
    from  myspider.items import MyspiderItem
    
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
    
        def parse(self, response):
            # print(response.text)
            a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract()
            for url in a_list:
                yield MyspiderItem(url=url)

    pipelines.py

    class MyspiderPipeline(object):
    
        def __init__(self,file_path):
            self.f = None
            self.file_path = file_path
    
        @classmethod
        def from_crawler(cls,crawler):
            '''
            执行pipeline类时,会先去类中找from_crawler的方法,
            如果有,则先执行此方法,并且返回一个当前类的对象,
            如果没有,则直接执行初始化方法
            :param crawler:
            :return:
            '''
            # 可以进行一些初始化之前的处理,比如:文件的路径配置到settings文件中,方便后期的更改。
            file_path = crawler.settings.get('PACHONG_FILE_PATH')
            return cls(file_path)
    
        def open_spider(self,spider):
            '''
            爬虫开始时被调用
            :param spider:
            :return:
            '''
            self.f = open(self.file_path,'w',encoding='utf8')
    
        def process_item(self, item, spider):
            '''
            执行持久化的逻辑操作
            :param item: 爬虫yield过来的item对象  (一个字典)
            :param spider:  爬虫对象
            :return:
            '''
            self.f.write(item['url']+'
    ')
            self.f.flush()   #将写入到内存的文件强刷到文件中,防止夯住,不使用此方法会夯住
            return item
    
        def close_spider(self,spider):
            '''
            爬虫结束时调用
            :param spider: 
            :return: 
            '''
            self.f.close()

    备注:执行pipeline时,会先找from_crawler方法,这个方法中,我们可以设置一些settings文件中的配置,通过crawler.settings得到一个settings对象(配置文件对象) <scrapy.settings.Settings object at 0x000002525581F908>

      执行pipeline中的process_item() 方法进行数据的持久化处理时,如果有多个pipeline(比如:将数据分别写入文件和数据库)时,先执行的pipeline(按照配置文件中数值的大小顺序执行),必须返回一个item对象,否则,后续的pipeline执行时,接收的item为None,无法进行数据的持久化操作,如果只是单纯的对某些数据进行一个持久化的处理,可以通过抛出异常,来阻止当前item对象后续的pipeline执行。抛出异常为:from scrapy.exceptions import DropItem       直接  raise DropItem()

      return不返回item对象与抛异常的区别:无返回值或者返回值为None时,后续的pipeline会执行,只是,此时的item为None,而抛出异常,会跳过当前对象后续的pipeline,执行下一个item对象。

    settings.py

    ITEM_PIPELINES = {  
      'myspider.pipelines.MyspiderPipeline': 300,
      'xxxxx.pipelines.FilePipeline': 400, 
    }
    # 每行后面的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。

    备注:数值小的先执行。

     获取所有页面

    import scrapy
    from  myspider.items import MyspiderItem
    from scrapy.http import Request
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
    
        def parse(self, response):
            a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract()
            for url in a_list:
                yield MyspiderItem(url=url)
    
            # 获取分页的url
            url_list = response.xpath('//div[@id="dig_lcpage"]//a/@href').extract()
            for url in url_list:
                url = 'https://dig.chouti.com%s'%url
                yield Request(url=url,callback=self.parse)

    备注:通过yield 每一个request对象,将所有的页面url添加到调度器中。

      scrapy框架会默认的将所有的结果进行去重操作。如果不去重,可以在request参数中,设置  dont_filter=True

     注意:settings.py中设置DEPTH_LIMIT = 1来指定“递归”的层数   ,这里的层数不是页码数
     
     在生成的每一个爬虫应用中,会有一个起始url,start_urls = ['https://dig.chouti.com/'],这个起始url执行完后会被parse回调函数接收响应结果。那我们如何修改这个回调函数呢?
      其实,在每一个爬虫应用继承的父类中,会执行一个方法  start_requests ,这个方法,会将起始的url生成一个request对象,传给调度器。
    class Spider(object_ref):
    def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)

     备注:在执行爬虫应用时,会先执行start_requests方法,所以我们可以重写此方法自定制。

    获取响应数据中的cookie

    返回的response中,无法通过  .cookies 获取cookie,只能通过从响应头中获取,但是获取的结果还得需要解析。

    {b'Server': [b'Tengine'], b'Content-Type': [b'text/html; charset=UTF-8'], b'Date': [
    b'Fri, 20 Jul 2018 13:43:42 GMT'], b'Cache-Control': [b'private'], b'Content-Language': [b'en'],
    b'Set-Cookie': [b'gpsd=5b05bcae8c6f4a273a53addfc8bbff22; domain=chouti.com; path=/; expires=Sun,
    19-Aug-2018 13:43:42 GMT
    ', b'JSESSIONID=aaadbrXmU-Jh2_kvbaysw; path=/'], b'Vary': [b'Accept-Encoding'],
    b'Via': [b'cache15.l2nu29-1[69,0], kunlun9.cn125[73,0]'], b'Timing-Allow-Origin': [b'*'],
    b'Eagleid': [b'6a78b50915320942226762320e']}

    所以,要通过scrapy封装的方法,将cookie解析出来

    import scrapy
    from scrapy.http.cookies import CookieJar
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/']
        cookie_dict = {}
        def parse(self, response):
    
            cookie_jar = CookieJar()
            cookie_jar.extract_cookies(response,response.request)   
            for k, v in cookie_jar._cookies.items():
                for i, j in v.items():
                    for m, n in j.items():
                        self.cookie_dict[m] = n.value
            print(self.cookie_dict)

    备注:CookieJar中封装的内容特别丰富,print(cookie_jar._cookies)   包含很多

    {'.chouti.com': {'/': {'gpsd': Cookie(version=0, name='gpsd', value='fcb9b9da7aaede0176d2a88cde8b6adb',
    port=None, port_specified=False, domain='.chouti.com', domain_specified=True, domain_initial_dot=False,
    path='/', path_specified=True, secure=False, expires=1534688487, discard=False, comment=None,
    comment_url=None, rest={}, rfc2109=False)}}, 'dig.chouti.com': {'/': {'JSESSIONID':
    Cookie(version=0, name='JSESSIONID', value='aaa4GWMivXwJf6ygMaysw', port=None, port_specified=False,
    domain='dig.chouti.com', domain_specified=False, domain_initial_dot=False, path='/',
    path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={},
    rfc2109=False)}}}

    自动登录抽屉并点赞和取消赞代码示例

    import scrapy
    from scrapy.http.response.html import HtmlResponse
    # import sys,os,io
    # sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
    from ..items import XzxItem
    from scrapy.http import Request
    from scrapy.http.cookies import CookieJar
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
        start_urls = ['https://dig.chouti.com/r/ask/hot/1']
        cookie_dict = {}
        def start_requests(self):
            for url in self.start_urls:
                yield Request(url=url,callback=self.parse)
    
        def parse(self, response):
            # 1. 去第一次访问页面中获取cookie
            # print(response.headers['Set-Cookie'],type(response.headers['Set-Cookie']))
            cookie_jar = CookieJar() #
            cookie_jar.extract_cookies(response, response.request) # cookie_jar中包含了cookie
    
    
            for k, v in cookie_jar._cookies.items():
                for i, j in v.items():
                    for m, n in j.items():
                        self.cookie_dict[m] = n.value
            # 2. 向https://dig.chouti.com/login发送POST请求
            yield Request(
                url='https://dig.chouti.com/login',
                method='POST',
                body="phone=8615901492719&password=qwer1234&oneMonth=1",
                cookies=self.cookie_dict,
                headers={
                    'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
                    'content-type':'application/x-www-form-urlencoded; charset=UTF-8',
                },
                callback=self.check_login
            )
    
        def check_login(self,response):
            print(response.text)
            yield Request(url='https://dig.chouti.com/',callback=self.index)
    
        def index(self,response):
    
            news_id_list = response.xpath('//div[@id="content-list"]//div[@class="part2"]/@share-linkid').extract()
            for news_id in news_id_list:
                #
                """
                news_url = "https://dig.chouti.com/link/vote?linksId=%s" %(news_id,)
                yield Request(
                    url=news_url,
                    method='POST',
                    cookies=self.cookie_dict,
                    callback=self.output
                )
                """
                # 取消赞
                news_url = "https://dig.chouti.com/vote/cancel/vote.do"
                yield Request(
                    url=news_url,
                    method='POST',
                    body="linksId=%s" %(news_id,),
                    cookies=self.cookie_dict,
                    headers={
                        'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
                    },
                    callback=self.output
                )
    
            # 2.1 获取页面
            page_list = response.xpath('//*[@id="dig_lcpage"]//a/@href').extract()
            for url in page_list:
                url = "https://dig.chouti.com" + url
                yield Request(url=url,callback=self.index)
    
        def output(self,response):
            print(response.text)
    View Code

    备注:爬取过程中的坑:请求头中,一定要携带content-type参数。请求过程中的url不能重复,尤其是和起始url。

    我们可以使用urllib中的urlencode帮我们把数据转化为formdata格式的
    from urllib.parse import urlencode
    
    ret = {'name':'xxx','age':18}
    
    print(urlencode(ret))
     
  • 相关阅读:
    Asp.Net Core Web MVC 调用Grpc,采用依赖注入
    .Net Core框架下 Grpc四种处理方法
    信息系统项目管理师高频考点(第二章)
    系统集成项目管理工程师高频考点(第二章)
    .Net Core框架下实现Grpc客户端和服务端
    .Net Framework框架下实现Grpc客户端和服务端
    Asp.Net Core Mvc项目登录IdentityServer4验证无法跳转问题
    IdentityServer4(五)
    MVC项目登录IdentityServer4报错, The cookie '.AspNetCore.Correlation has set 'SameSite=None' and must also set 'Secure'
    IdentityServer4(二)
  • 原文地址:https://www.cnblogs.com/zhaopanpan/p/9339784.html
Copyright © 2020-2023  润新知