zoukankan      html  css  js  c++  java
  • Scrapy框架

    Scrapy介绍

    #1  通用的网络爬虫框架,爬虫界的django
    #2 scrapy执行流程
        5大组件
            -引擎(EGINE):大总管,负责控制数据的流向
            -调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去除重复的网址(集合,布隆过滤器)
                -深度优先
                    -摁着一条线爬取
                    -先进先出
                -广度优先
                    -后进先出
                    
            -下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
            -爬虫(SPIDERS):开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求request
            -项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
        2大中间件     
            -爬虫中间件:位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少)
            -下载中间件:引擎和下载器之间,加代理,加头,集成selenium        
            
    # 3 开发者只需要在固定的位置写固定的代码即可(写的最多的spider)

    Scrapy安装

    #1 pip3 install scrapy(mac,linux)
    #2 windows上(80%能成功,少部分人成功不了)
        1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
        3、pip3 install lxml
        4、pip3 install pyopenssl
        5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
        6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
        7、执行pip3 install 下载目录Twisted-17.9.0-cp36-cp36m-win_amd64.whl
        8、pip3 install scrapy
    # 3 就有scrapy命令
        -D:Python36Scriptsscrapy.exe  用于创建项目

    Scrapy创建项目,创建爬虫,运行爬虫

    1 scrapy startproject 项目名
        -scrapy startproject firstscrapy
    2 创建爬虫
        -scrapy genspider example example.com
        -scrapy genspider 爬虫名 爬虫地址
        -scrapy genspider chouti dig.chouti.com
        -一执行就会在spider文件夹下创建出一个py文件,名字叫chouti
    3 运行爬虫
        -scrapy crawl chouti   # 带运行日志
        -scrapy crawl chouti --nolog  # 不带日志
    4 支持右键执行爬虫
        -在项目路径下新建一个main.py
        from scrapy.cmdline import execute
        execute(['scrapy','crawl','chouti','--nolog'])

    Scrapy目录介绍

        # 目录介绍
        firstscrapy  # 项目名字
            firstscrapy #
                -spiders # 所有的爬虫文件放在里面
                    -baidu.py # 一个个的爬虫(以后基本上都在这写东西)
                    -chouti.py
                -middlewares.py # 中间件(爬虫,下载中间件都写在这)
                -pipelines.py   # 持久化相关写在这(items.py中类的对象)
                -main.py        # 自己加的,执行爬虫
                -items.py       # 一个一个的类,
                -settings.py    # 配置文件
            scrapy.cfg          # 上线相关

    settings介绍

    1 默认情况,scrapy会去遵循爬虫协议
    2 修改配置文件参数,强行爬取,不遵循协议
        -ROBOTSTXT_OBEY = False
    3 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
    4 LOG_LEVEL='ERROR'

    爬取抽屉新闻

    import scrapy
    from bs4 import BeautifulSoup
    from scrapy.http.request import Request
    from firstscrapy.items import ChoutiItem
    
    class ChoutiSpider(scrapy.Spider):
        name = 'chouti'
        allowed_domains = ['dig.chouti.com']
        start_urls = ['http://dig.chouti.com/']
    
        #
        #     # 使用第三方解析
        #     # def parse(self, response):
        #     #     # print(response.text)
        #     #     # 解析数据(第一种方案,自己解析,bs4,lxml)
        #     #     soup = BeautifulSoup(response.text,'lxml')
        #     #     divs = soup.find_all(class_='link-title')
        #     #     for div in divs:
        #     #         print(div.text)
        #
        #     # 想继续爬取其他网址
        #     # def parse(self, response):
        #     #     # 以后解析都在这里
        #     #     print(response.status)
        #     #     # 假设解析出一个网址(继续爬取)
        #     #     return Request('https://www.baidi.com/',dont_filter=True)
        #
        #     # 使用自带解析
        #     # xpath
        #     # xpath:response.xpath('xpath语法').extract()  取所有
        #     # xpath:response.xpath('xpath语法').extract_first()  取一个
        #
        #     # css
        #     # response.css('.link-title::text').extract()  # 取文本
        #     # response.css('.link-title::attr(href)').extract()[0]  # 取属性
        #     def parse(self, response):
        #         # 只有css和xpath
        #         # title_list = response.css('.link-title')
        #         # print(len(title_list))
        #         # title_list = response.xpath('//a[contains(@class,"link-title")]/text()').extract()
        #         # title_list = response.xpath('//a[contains(@class,"link-title")]').extract()
        #         # print(len(title_list))
        #         # print(title_list)
        #
        #         # 解析出所有的标题和图片地址
        #         div_list = response.xpath('//div[contains(@class,"link-item")]')
        #         for div in div_list:
        #             # extract() 取出列表 即便有一个也是列表
        #             # extract_first() 取出第一个值
        #             # title = div.css('.link-title::text').extract()
        #             # title = div.css('.link-title::text').extract_first()
        #             url = div.css('.link-title::attr(href)').extract()[0]
        #             # print(title)
        #             print(url)
    
        # 持久化方案一
        # def parse(self, response, **kwargs):
        #     ll = []
        #     div_list = response.xpath('//div[contains(@class,"link-item")]')
        #     for div in div_list:
        #         title = div.css('.link-title::text').extract()
        #         url = div.css('.link-title::attr(href)').extract_first()
        #         phone_url = div.css('.image-scale::attr(src)').extract_first()
        #         # 持久化:方案一(用得少)parser必须返回列表套字典的形式
        #         ll.append({'title': title, 'url': url, 'phone_url': phone_url})
        #     return ll
    
        # 持久化方案二
        def parse(self, response):
    
            div_list = response.xpath('//div[contains(@class,"link-item")]')
            for div in div_list:
                item = ChoutiItem()
                title = div.css('.link-title::text').extract_first()
                url = div.css('.link-title::attr(href)').extract_first()
                photo_url = div.css('.image-scale::attr(src)').extract_first()
                if not photo_url:
                    photo_url = ''
                # item.title=title
                # item.url=url
                # item.photo_url=photo_url
                item['title'] = title
                item['url'] = url
                item['photo_url'] = photo_url
                yield item
    View Code

    Scrapy的数据解析

    #xpath:
        -response.xpath('//a[contains(@class,"link-title")]/text()').extract()  # 取文本
        -response.xpath('//a[contains(@class,"link-title")]/@href').extract()  #取属性
    #css
        -response.css('.link-title::text').extract()  # 取文本
        -response.css('.link-title::attr(href)').extract_first()  # 取属性

    Scrapy的持久化存储

    #1 方案一:parser函数必须返回列表套字典的形式(了解)
        scrapy crawl chouti -o chouti.csv
    #2 方案二:高级,pipline item存储(mysql,redis,文件)
        -在Items.py中写一个类
        -在spinder中导入,实例化,把数据放进去
                item['title']=title
                item['url']=url
                item['photo_url']=photo_url
                yield item
                
        -在setting中配置(数字越小,级别越高)
            ITEM_PIPELINES = {
               'firstscrapy.pipelines.ChoutiFilePipeline': 300,
            }
        -在pipelines.py中写ChoutiFilePipeline
            -open_spider(开始的时候)
            -close_spider(结束的时候)
            -process_item(在这持久化)

    自动给抽屉点赞

    from selenium import webdriver
    import time
    import requests
    
    bro=webdriver.Chrome(executable_path='./chromedriver.exe')
    bro.implicitly_wait(10)
    bro.get('https://dig.chouti.com/')
    
    login_b=bro.find_element_by_id('login_btn')
    print(login_b)
    login_b.click()
    
    username=bro.find_element_by_name('phone')
    username.send_keys('18953675221')
    password=bro.find_element_by_name('password')
    password.send_keys('lqz123')
    
    button=bro.find_element_by_css_selector('button.login-btn')
    button.click()
    # 可能有验证码,手动操作一下
    time.sleep(10)
    
    
    my_cookie=bro.get_cookies()  # 列表
    print(my_cookie)
    bro.close()
    
    # 这个cookie不是一个字典,不能直接给requests使用,需要转一下
    cookie={}
    for item in my_cookie:
        cookie[item['name']]=item['value']
    
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
        'Referer': 'https://dig.chouti.com/'}
    # ret = requests.get('https://dig.chouti.com/',headers=headers)
    # print(ret.text)
    
    
    ret=requests.get('https://dig.chouti.com/top/24hr?_=1596677637670',headers=headers)
    print(ret.json())
    ll=[]
    for item in ret.json()['data']:
        ll.append(item['id'])
    
    print(ll)
    for id in ll:
        ret=requests.post(' https://dig.chouti.com/link/vote',headers=headers,cookies=cookie,data={'linkId':id})
        print(ret.text)
    
    'https://dig.chouti.com/comments/create'
    '''
    content: 说的号
    linkId: 29829529
    parentId: 0
    
    '''

    全站爬取cnblogs

    import scrapy
    from cnblogs.items import CnblogsItem
    from scrapy import Request
    from selenium import webdriver
    from scrapy.dupefilters import RFPDupeFilter
    
    
    
    
    
    class CnblogSpider(scrapy.Spider):
        name = 'cnblog'
        allowed_domains = ['www.cnblogs.com']
        start_urls = ['https://www.cnblogs.com/']
        bro = webdriver.Chrome(executable_path='../../chromedriver.exe')
    
        '''
        爬取原则:scrapy默认是先进先出
            -深度优先:详情页先爬  队列:先进去先出来
            -广度优先:每一页先爬  栈:先进后出
        '''
        def parse(self, response):
            # print(response.text)
            div_list = response.css('article.post-item')
            for div in div_list:
                item = CnblogsItem()
                title = div.xpath('.//div[1]/a/text()').extract_first()
                item['title'] = title
                url = div.xpath('.//div[1]/a/@href').extract_first()
                item['url'] = url
                desc = div.xpath('string(.//div[1]/p)').extract_first().strip()
                item['desc'] = desc
                # print(title)
                # print(url)
                # print(desc)
    
                # 继续爬取详情
                # callback如果不写默认回调到parse方法
                # 如果写了,响应回来的对象就会调到自己写的解析方法中
                # 请求和响应之间传递参数,使用meta
    
                yield Request(url, callback=self.parser_detail, meta={'item': item})
    
            # 解析出下页的地址
            next = 'https://www.cnblogs.com' + response.css('#paging_block>div a:last-child::attr(href)').extract_first()
            # print(next)
            yield Request(next)
    
        def parser_detail(self, response):
            content = response.css('#cnblogs_post_body').extract_first()
            print(str(content))
            # item哪里来
            item = response.meta.get('item')
            item['content'] = content
            yield item
    View Code

    Scrapy的请求传参

    # 把要传递的数据放到meta中
    yield Request(urlmeta={'item':item})
    # 在response对象中取出来
    item=response.meta.get('item')

    提升scrapy爬取数据的效率

    - 在配置文件中进行相关的配置即可:(默认还有一套setting)
    #1 增加并发:
    IO密集型
    默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
    
    #2 降低日志级别:
    在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
    
    # 3 禁止cookie:
    如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
    
    # 4禁止重试:
    对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
    
    # 5 减少下载超时:
    如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

    scrapy的中间件(下载中间件)

    # 1 都写在middlewares.py
    # 2 爬虫中间件
    # 3 下载中间件
    # 4 要生效,一定要配置,配置文件
    
    
    # 下载中间件
    -process_request:返回不同的对象,后续处理不同(加代理...)
              # 1 更换请求头
            # print(type(request.headers))
            # print(request.headers)
            #
            # from scrapy.http.headers import Headers
            # request.headers['User-Agent']=''
    
            # 2 加cookie ---cookie池
            # 假设你你已经搭建好cookie 池了,
            # print('00000--',request.cookies)
            # request.cookies={'username':'asdfasdf'}
    
            # 3 加代理
            # print(request.meta)
            # request.meta['download_timeout'] = 20
            # request.meta["proxy"] = 'http://27.188.62.3:8060'
    -process_response:返回不同的对象,后续处理不同
    - process_exception
    def process_exception(self, request, exception, spider):
            print('xxxx')
            # 不允许直接改url
            # request.url='https://www.baidu.com'
            from scrapy import Request
            request=Request(url='https://www.baidu.com',callback=spider.parser)
            return request

    selenium在scrapy中的使用流程

    # 当前爬虫用的selenium是同一个
    
    # 1 在爬虫中初始化webdriver对象
        from selenium import webdriver
        class CnblogSpider(scrapy.Spider):
            name = 'cnblog'
            ...
     bro=webdriver.Chrome(executable_path='../chromedriver.exe')
    # 2 在中间件中使用(process_request)
    spider.bro.get('https://dig.chouti.com/')   response=HtmlResponse(url='https://dig.chouti.com/',body=spider.bro.page_source.encode('utf-8'),request=request)
        return response
        
    # 3 在爬虫中关闭
        def close(self, reason):
            print("我结束了")
            self.bro.close()

    去重规则源码分析

    from scrapy.dupefilters import RFPDupeFilter
    # 详见代码

    布隆过滤器

    详情参见:https://www.cnblogs.com/xiaoyuanqujing/protected/articles/11969224.html
    
    极小内存校验是否重复
    
    #python3.6 安装
    #需要先安装bitarray
    pip3 install bitarray-0.8.1-cp36-cp36m-win_amd64.whl(pybloom_live依赖这个包,需要先安装)
    #下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/
    pip3 install pybloom_live

    分布式爬虫(scrapy-redis)

    # 1 pip3 install scrapy-redis
    # 2 原来继承Spider,现在继承RedisSpider
    # 3 不能写start_urls = ['https:/www.cnblogs.com/']
    # 4 需要写redis_key = 'myspider:start_urls'
    # 5 setting中配置:
    
    # redis的连接
    REDIS_HOST = 'localhost'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
        # 使用scrapy-redis的去重
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis的Scheduler
    # 分布式爬虫的配置
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 持久化的可以配置,也可以不配置
    ITEM_PIPELINES = {
       'scrapy_redis.pipelines.RedisPipeline': 299
    }
    
    
    # 9现在要让爬虫运行起来,需要去redis中以myspider:start_urls为key,插入一个起始地址lpush myspider:start_urls https://www.cnblogs.com/

    破解知乎登录(js逆向和解密)

    client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&
    grant_type=password&
    timestamp=1596702006088&
    source=com.zhihu.web&
    signature=eac4a6c461f9edf86ef33ef950c7b6aa426dbb39&
    username=%2B86liuqingzheng&
    password=1111111&
    captcha=&
    lang=en&
    utm_source=&
    ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"
    
    
    # 破解知乎登陆
    
    import requests    #请求解析库
    
    import base64                              #base64解密加密库
    from PIL import Image                        #图片处理库
    import hmac                                  #加密库
    from hashlib import sha1                  #加密库
    import time
    from urllib.parse import urlencode          #url编码库
    import execjs                              #python调用node.js
    from http import cookiejar as cookielib
    class Spider():
        def __init__(self):
            self.session = requests.session()
            self.session.cookies = cookielib.LWPCookieJar()    #使cookie可以调用save和load方法
            self.login_page_url = 'https://www.zhihu.com/signin?next=%2F'
            self.login_api = 'https://www.zhihu.com/api/v3/oauth/sign_in'
            self.captcha_api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
            self.headers = {
                'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER',
            }
    
            self.captcha =''         #存验证码
            self.signature = ''       #存签名
    
        # 首次请求获取cookie
        def get_base_cookie(self):
            self.session.get(url=self.login_page_url, headers=self.headers)
    
        def deal_captcha(self):
            r = self.session.get(url=self.captcha_api, headers=self.headers)
            r = r.json()
            if r.get('show_captcha'):
                while True:
                    r = self.session.put(url=self.captcha_api, headers=self.headers)
                    img_base64 = r.json().get('img_base64')
                    with open('captcha.png', 'wb') as f:
                        f.write(base64.b64decode(img_base64))
                    captcha_img = Image.open('captcha.png')
                    captcha_img.show()
                    self.captcha = input('输入验证码:')
                    r = self.session.post(url=self.captcha_api, data={'input_text': self.captcha},
                                          headers=self.headers)
                    if r.json().get('success'):
                        break
    
        def get_signature(self):
            # 生成加密签名
            a = hmac.new(b'd1b964811afb40118a12068ff74a12f4', digestmod=sha1)
            a.update(b'password')
            a.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20')
            a.update(b'com.zhihu.web')
            a.update(str(int(time.time() * 1000)).encode('utf-8'))
            self.signature = a.hexdigest()
    
        def post_login_data(self):
            data = {
                'client_id': 'c3cef7c66a1843f8b3a9e6a1e3160e20',
                'grant_type': 'password',
                'timestamp': str(int(time.time() * 1000)),
                'source': 'com.zhihu.web',
                'signature': self.signature,
                'username': '+8618953675221',
                'password': '',
                'captcha': self.captcha,
                'lang': 'en',
                'utm_source': '',
                'ref_source': 'other_https://www.zhihu.com/signin?next=%2F',
            }
    
            headers = {
                'x-zse-83': '3_2.0',
                'content-type': 'application/x-www-form-urlencoded',
                'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER',
            }
    
            data = urlencode(data)
            with open('zhih.js', 'rt', encoding='utf-8') as f:
                js = execjs.compile(f.read(), cwd='node_modules')
            data = js.call('b', data)
    
            r = self.session.post(url=self.login_api, headers=headers, data=data)
            print(r.text)
            if r.status_code == 201:
                self.session.cookies.save('mycookie')
                print('登录成功')
            else:
                print('登录失败')
    
        def login(self):
            self.get_base_cookie()
            self.deal_captcha()
            self.get_signature()
            self.post_login_data()
    if __name__ == '__main__':
        zhihu_spider = Spider()
        zhihu_spider.login()

    爬虫的反扒措施

    1 user-agent
    2 referer
    3 cookie(cookie池,先访问一次)
    4 频率限制(代理池,延迟)
    5 js加密(扣出来,exjs模块执行)
    6 css加密
    7 验证码(打码平台),半手动
    8 图片懒加载

     

  • 相关阅读:
    「UVA12293」 Box Game
    「CF803C」 Maximal GCD
    「CF525D」Arthur and Walls
    「CF442C」 Artem and Array
    LeetCode lcci 16.03 交点
    LeetCode 1305 两棵二叉搜索树中的所有元素
    LeetCode 1040 移动石子直到连续 II
    LeetCode 664 奇怪的打印机
    iOS UIPageViewController系统方法崩溃修复
    LeetCode 334 递增的三元子序列
  • 原文地址:https://www.cnblogs.com/ZhZhang12138/p/14885782.html
Copyright © 2011-2022 走看看