zoukankan      html  css  js  c++  java
  • python 爬虫二

    内容回顾

    - 模拟登陆:
    - sometimes我们需要爬取基于当前用户的用户信息(需要登录后才可查看)
    - 实现流程:
    - 借助于抓包工具,抓取点击登录按钮发起的post请求(url,参数(动态参数))
    - 携带cookie对其他子页面进行请求发送
    - 反爬机制:
    - robots
    - UA检测
    - 验证码
    - cookie
    - 禁ip
    - 动态请求参数

    在程序中 是否可以一味的使用多线程,多进程?

    (推荐)使用单线程+多任务异步协程

    event_loop:事件循环,相当于一个无限循环,我们可以把一些特殊函数注册(放置)到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
    程序是按照设定的顺序从头执行到尾,运行的次数也是完全按照设定。当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,让另一部分的程序先运行起来。
    当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。loop就是这个持续不断的监视器。
    coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,
    它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,
    而是返回一个协程对象。
    task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
    future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
    另外我们还需要了解 async/await 关键字,它是从 Python 3.6 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
    了解携程概念基础后,我们来看看一个协程,具体需要什么?

    事件循环-就是一个无限的循环
    协程:特殊的函数 async修饰之后 这个函数调用不会立刻执行 会封转到协程对象,只有协程注册到任务对象中,并且任务对象注册事件循环中 才会执行
    await:只要有阻塞操作 一定要在前面加await

    协程基础

    import asyncio
    async def request(url):
        print('正在请求',url)
        print('下载成功',url)
    c=request('www.baidu.com')
    
    #实例化一个事件对象
    loop=asyncio.get_event_loop()
    #创建一个任务对象 将协程封装到该对象中 #task=loop.create_task(c) #第二种任务对象 task=asyncio.ensure_future(c) #将协程对象注册到事件循环对象中,并启动事件循环对象 loop=run_until_complete(task)

    多任务异步协程

    from time import sleep
    import asyncio
    import time
    urls = ['www.baidu.com','www.sogou.com','www.goubanjia.com']
    start = time.time()
    async def request(url):
        print('正在请求:',url)
        #在多任务异步协程实现中,不可以出现不支持异步的相关代码。
        # sleep(2)
        await asyncio.sleep(2)
        print('下载成功:',url)
    
    #实例化一个事件循环对象
    loop = asyncio.get_event_loop()
    
    #任务列表:放置多个任务对象
    tasks = []
    for url in urls:
        c = request(url)
        #实例化任务对象的方法
        task = asyncio.ensure_future(c)
        tasks.append(task)
    
    #将协程对象注册到事件循环中,并且我们需要启动事件循环
    loop.run_until_complete(asyncio.wait(tasks))
    
    print(time.time()-start)

    多任务异步协程应用到爬虫中

    可以看到 每次访问 都需要2秒 一种是6秒

    from flask import Flask
    import time
    
    app = Flask(__name__)
    
    
    @app.route('/bobo')
    def index_bobo():
        time.sleep(2)
        return 'Hello bobo'
    
    @app.route('/jay')
    def index_jay():
        time.sleep(2)
        return 'Hello jay'
    
    @app.route('/tom')
    def index_tom():
        time.sleep(2)
        return 'Hello tom'
    
    if __name__ == '__main__':
        app.run(threaded=True)
    #aiohttp:支持异步的一个基于网络请求的模块
    # pip install aiohttp
    import asyncio
    import aiohttp
    import time
    #单线程+多任务异步协程
    urls = [
        'http://127.0.0.1:5000/jay',
        'http://127.0.0.1:5000/bobo',
        'http://127.0.0.1:5000/tom'
    ]
    #代理操作有变化:
    #async with await s.get(url,proxy="http://ip:port") as response:
    async def get_pageText(url):
        async with aiohttp.ClientSession() as s:#实例化一个请求对象
            async  with await s.get(url) as response:#get方法
                page_text=await response.text()#只要有阻塞操作就使用await 获取返回值
                #借助回调函数进行响应数据的解析操作
                return page_text
    #封装回调函数用于数据解析
    def parse(task):
        #1.获取响应数据
        page_text = task.result()
        print(page_text+',即将进行数据解析!!!')
        #解析操作写在该位置
    
    start=time.time()
    tasks=[]
    for url in urls:
        c=get_pageText(url)#使用这个函数
        task=asyncio.ensure_future(c)#对协程的一种封装
        task.add_done_callback(parse) # 给任务对象绑定回调函数用于数据解析
        tasks.append(task)
    
    loop=asyncio.get_event_loop()#创建一个事件对象
    loop.run_until_complete(asyncio.wait(tasks))#将协程注册到循环中,并启用这个循环
    print(time.time()-start)

     selenium

    概念:是一个基于浏览器自动话的模块.

    和爬虫之间的关联?

      帮我们便捷的爬取到页面中动态加载出来的数据

      实现模拟登陆

    使用流程:

    pip install selenium

    下载对应的驱动程序:http://chromedriver.storage.googleapis.com/index.html

    查看对应的版本:https://blog.csdn.net/huilan_same/article/details/51896672

    selenium简单应用

    from  selenium import webdriver
    from lxml import etree
    import  time
    bro=webdriver.Chrome(executable_path='./chromedriver.exe')#导入驱动
    #让浏览器对指定url发起访问
    bro.get('http://125.35.6.84:81/xk/')
    #获取浏览器当前打开页面源码(可见既可得)
    page_text=bro.page_source
    time.sleep(2)
    tree=etree.HTML(page_text)
    name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
    print(name)
    time.sleep(2)
    bro.quit()
    ####################打开淘宝输入想搜索的值###########################
    from selenium import webdriver
    import time
    bro=webdriver.Chrome(executable_path='./chromedriver.exe')
    bro.get('https://www.taobao.com')
    #节点定位find系列的方法
    input_text=bro.find_element_by_id('q')#找到id为q的
    #节点交互
    input_text.send_keys('苹果')#在text中输入苹果
    time.sleep(2)

    动作链

    from selenium import webdriver
    #导入动作链对应的模块
    from  selenium.webdriver import ActionChains
    import time
    bro=webdriver.Chrome(executable_path='./chromedriver.exe')
    bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
    #如果定位的节点是被包含在iframes节点之中的,则必须使用switch_to进行frame的切换
    bro.switch_to.frame('iframeResult') #iframe
    
    div_tag=bro.find_element_by_id('draggable')#获取id
    #实例化一个动作链对象(需要将浏览器对象作为参数传递给该对象的构造方法)
    action=ActionChains(bro)
    #单击且长按
    action.click_and_hold(div_tag)
    
    for i in range(5):
        #让div向右移动
        action.move_by_offset(17,0).perform()#移动17*5 perform立刻执行动作链
        time.sleep(0.5)
    time.sleep(2)
    bro.quit()#退出

    无头浏览器

    from selenium import webdriver
    from lxml import etree
    import time
    
    from selenium.webdriver.chrome.options import Options
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    
    bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)
    #让浏览器对指定url发起访问
    bro.get('http://125.35.6.84:81/xk/')
    
    #获取浏览器当前打开页面的页面源码数据(可见即可得)
    page_text = bro.page_source
    time.sleep(2)
    tree = etree.HTML(page_text)
    name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
    print(name)
    time.sleep(3)
    bro.quit()

    selenium规避被检测

    from selenium import webdriver
    from lxml import etree
    import time
    #规避检查
    from selenium.webdriver import ChromeOptions
    option = ChromeOptions()
    option.add_experimental_option('excludeSwitches', ['enable-automation'])
    
    bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option)
    #让浏览器对指定url发起访问
    bro.get('http://125.35.6.84:81/xk/')
    
    #获取浏览器当前打开页面的页面源码数据(可见即可得)
    page_text =bro.page_source
    time.sleep(2)
    tree=etree.HTML(page_text)
    name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
    print(name)
    time.sleep(5)
    bro.quit()

    模拟QQ空间

    import request
    from selenium import webdriver
    import time
    
    driver=webdriver.Chrome(executable_path='./chromedriver.exe')
    driver.get('https://qzone.qq.com/')
    # 在web 应用中经常会遇到frame 嵌套页面的应用,使用WebDriver 每次只能在一个页面上识别元素,对于frame 嵌套内的页面上的元素,直接定位是定位是定位不到的。这个时候就需要通过switch_to_frame()方法将当前定位的主体切换了frame 里。
    driver.switch_to.frame('login_frame')
    driver.find_element_by_id('switcher_plogin').click()
    driver.find_element_by_id('u').send_keys('1820405927')  # 这里填写你的QQ号
    driver.find_element_by_id('p').send_keys('xxxxxx')  # 这里填写你的QQ密码
    driver.find_element_by_id('login_button').click()
    time.sleep(2)
    driver.close()

    pyppeteer 

     类是于一个centos的selenium 使用谷歌测试版的浏览器

    超级鹰自动登陆12306

    pip install Pillow

    chaojiying_api.py

    import requests
    from hashlib import md5
    
    
    class Chaojiying_Client(object):
    
        def __init__(self, username, password, soft_id):
            self.username = username
            password = password.encode('utf8')
            self.password = md5(password).hexdigest()
            self.soft_id = soft_id  # 898175
            self.base_params = {
                'user': self.username,
                'pass2': self.password,
                'softid': self.soft_id,  # 898175
            }
            self.headers = {
                'Connection': 'Keep-Alive',
                'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
            }
    
        def PostPic(self, im, codetype):
            """
            im: 图片字节
            codetype: 题目类型 参考 http://www.chaojiying.com/price.html
            """
            params = {
                'codetype': codetype,
            }
            params.update(self.base_params)
            files = {'userfile': ('ccc.jpg', im)}
            r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                              headers=self.headers)
            return r.json()
    
        def ReportError(self, im_id):
            """
            im_id:报错题目的图片ID
            """
            params = {
                'id': im_id,
            }
            params.update(self.base_params)
            r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
            return r.json()

    12306程序

    from selenium import webdriver
    import time
    from selenium.webdriver import ActionChains
    from PIL import Image#切图
    from chaojiying_api import Chaojiying_Client
    bro=webdriver.Chrome(executable_path=r'./chromedriver.exe')
    bro.get('https://kyfw.12306.cn/otn/login/init')
    
    time.sleep(2)
    code_img_ele=bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
    time.sleep(2)
    #验证码图片的左上角的坐标
    location=code_img_ele.location#x,y
    print('验证码图片左上角的坐标',location)
    size=code_img_ele.size#验证码图片的长和宽
    print('验证码图片的长和宽size',size)
    #验证码左上角和右下角这2个点的坐标
    rangle=(
        int(location['x']),
        int(location['y']),
        int(location['x'] + size['width']),
        int(location['y'] + size['height'])
    
    )
    #截取当前浏览器打开的这张页面的图像
    bro.save_screenshot('aa.png')
    
    i=Image.open('./aa.png')#读取这个图片
    #截取下来验证码图片名称
    code_img_name='./code.png'
    #crop就可以根据左上角 和右下角的坐标进行指定区域的截取
    frame=i.crop(rangle)
    frame.save(code_img_name)
    
    import requests
    chaojiying = Chaojiying_Client('xuebaohua', '6xbh', '898175')  # 用户中心>>软件ID 生成一个替换 96001
    im=open('./code.png','rb').read()
    result=chaojiying.PostPic(im,9004)#9004验证类型
    print("***************", result)
    result=result['pic_str']#取得坐标
    all_list = [] #[[x1,y1],[x2,y2],[x3,y3]]
    if '|' in result:  # 117,75|188,146|117,75
        list_1 = result.split('|')
        count_1 = len(list_1)
        for i in range(count_1):
            xy_list = []
            x = int(list_1[i].split(',')[0])   #[[x,y],[]]
            y = int(list_1[i].split(',')[1])
            xy_list.append(x)
            xy_list.append(y)
            all_list.append(xy_list)
        print(all_list,'11111')#存储的形式 [[260, 62], [46, 139]] 11111
    else:
        x = int(result.split(',')[0])   #[[x,y]]
        y = int(result.split(',')[1])
        xy_list = []
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
    print(all_list)#[[251, 76]]
    code_img = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
    
    action = ActionChains(bro)#进行动作链
    for l in all_list:
        x=l[0]
        y=l[1]                                       #拿到截图图片进行过便宜
        ActionChains(bro).move_to_element_with_offset(code_img, x, y).click().perform()
    
    bro.find_element_by_id('username').send_keys('135022')
    time.sleep(2)
    bro.find_element_by_id('password').send_keys('166')
    time.sleep(2)
    bro.find_element_by_id('loginSub').click()
    
    
    '''
    验证码图片左上角的坐标 {'x': 278, 'y': 274}
    验证码图片的长和宽size {'height': 190, 'width': 293}
    *************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '3069614092051000215', 'pic_str': '251,76', 'md5': '93d0b89555d109be569a44c5a3b997fe'}
    [[251, 76]]
    '''
    '''
    #2个12306验证码的数据
    验证码图片左上角的坐标 {'x': 278, 'y': 274}
    验证码图片的长和宽size {'height': 190, 'width': 293}
    *************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '9069614192051000217', 'pic_str': '260,62|46,139', 'md5': '818ec8e895f95dff73a86583890038b0'}
    [[260, 62], [46, 139]] 11111
    [[260, 62], [46, 139]]
    '''

    scrapy:爬虫框架 异步爬取,高性能的数据解析+持久化存储操作

    框架:集成了很多功能且具有很强通用性的一个项目模版

    如何学习框架:

      学习框架的功能模块的具体使用

    环境的安装:

    linux:

      pip install scrapy

    windows:

          a. pip3 install wheel
    
          b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    
          c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl#借助实现异步
    
          d. pip3 install pywin32
    
          e. pip3 install scrapy

    scrapy:爬取糗事百科文字

    使用流程

      创建一个工程:scrapy startproject firstBlood

      cd firstBlood

      创建爬虫文件: scrapy genspider first www.baiud.com

      执行工程:scrapy crawl first --nolog(可选)

    命令行的:scrapy crawl qiutu -o qiutu.csv  执行保存(局限性比较强,只能是.csv)

    基于管道:

    命令行代码

    # -*- coding: utf-8 -*-
    import scrapy
    
    #爬虫类
    class FirstSpider(scrapy.Spider):
        #爬虫文件的名称
        name = 'qiutu'
        #允许的域名
        # allowed_domains = ['www.baiud.com']
        #起始的url列表 被进行自动的请求发送
        start_urls = ['https://www.qiushibaike.com/text/']
    
        #用来解析数据
        #持久化存储,只可以将parse方法的返回值存储到磁盘文件
        def parse(self, response):
            div_list=response.xpath('//*[@id="content-left"]/div')
            all_data=[]
            for div in div_list:
                # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
                author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
                content=div.xpath('.//div[@class="content"]/span//text()').extract()
                content=''.join(content)#将列表转成字符串
                dic={
                    'author':author,
                    'content':content
                }
                all_data.append(dic)
            return  all_data

    持久化存储代码

    items.py

    import scrapy
    
    
    class FirstbloodItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()#万能的数据类型
        author = scrapy.Field()
        content = scrapy.Field()

    first.py

    from ..items import FirstbloodItem
    #爬虫类
    class FirstSpider(scrapy.Spider):
        #爬虫文件的名称
        name = 'qiutu'
        #允许的域名
        # allowed_domains = ['www.baiud.com']
        #起始的url列表 被进行自动的请求发送
        start_urls = ['https://www.qiushibaike.com/text/']
        def parse(self, response):
            div_list=response.xpath('//*[@id="content-left"]/div')
            all_data=[]
            for div in div_list:
                # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
                author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
                # print(author,'11111')
                content=div.xpath('.//div[@class="content"]/span//text()').extract()
                content=''.join(content)#将列表转成字符串
                #实例化一个item类型的对象
                item=FirstbloodItem()
                #使用中括号的形式访问item对象中的属性
                item['author'] = author
                item['content'] = content
                #将item内容提交到管道
                yield item

    pipelines.py

    import pymysql
    from redis import Redis
    class FirstbloodPipeline(object):
        fp = None
        def open_spider(self,spider):#修改父类方法
            print('开始爬虫.......')
            self.fb=open('./qiutu.txt','w',encoding='utf-8')
        def process_item(self, item, spider):
            author=item['author']#取属性赋值
            content = item['content']
            self.fb.write(author+':'+content+'
    ')
            return item #返回给了下一个即将被执行的管道类
        def close_spider(self,spider):
            print('结束爬虫!!!')
            self.fb.close()
    
    class FirstbloodPipeline(object):
        conn=None
        cursor=None
        def open_spider(self,spider):
            self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456.com',db='qiutu')
            print(self.conn)
        def process_item(self,item,spider):
            self.cursor=self.conn.cursor()
            try:
                self.cursor.execute('insert into qiutu values("%s","%s")'%(item['author'],item['content']))
                self.conn.commit()
            except Exception as e:
                print(e)
                self.conn.rollback()#回滚
            return item
        def close_spider(self,seider):
            self.cursor.close()
            self.conn.close()

    scrapy:使用总结

    创建工程 scrapy startproject proname
    创建爬虫文件
        cd proname
        scrapy genspider spidername www.baidu.com
    爬虫相关属性方法
        爬虫文件的名称:name
        起始的url列表:start_urls 存储的url会被scrapy进行自动的请求发送
        parse(reponse):用来解析start_urls列表中url对应的响应数据
        response.xpath() extrct()
    数据持久化存储
    -基于终端指定
      只可以将parse方法的返回值进行持久化存储
      scrapy crawl spidername -o ./file
    -基础管道持久化存储的编码流程
      数据解析
      -在item类中声明相关的属性用于存储解析到数据
      -将解析到的数据存储封装到item类型对象中
      -将item对象提交给管道类
      -item会被管道类中的process_item方法的item参数进行接收
      -process_item方法中编写基于item持久化存储的操作
      -在配置文件中开启管道
    管道细节处理:
      -管道文件中一个类对应的是什么?
        一个类表达式将解析到数据存储到某一个具体的平台中
      -process_item方法中的返回值表示什么含义
        return item就是说将item传递给下一个即将被执行的管道类
      -open_spider#开始 close_spider#结束


    手动请求发送

    yield scrapy.Request(url,callback):callback回调一个函数用于数据解析

    爬取阳光网前5页数据

    import scrapy
    from sunLinePro.items import SunlineproItem
    
    class SunSpider(scrapy.Spider):
        name = 'sun'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
    
        #通用的url模板(不可以修改)
        url = 'http://wz.sun0769.com/index.php/question/questionType?type=4&page=%d'
        page = 1
    
        def parse(self, response):
            print('--------------------------page=',self.page)
            tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
            for tr in tr_list:
                title = tr.xpath('./td[2]/a[2]/text()').extract_first()
                status = tr.xpath('./td[3]/span/text()').extract_first()
    
                item = SunlineproItem()
                item['title'] = title
                item['status'] = status
    
                yield item
            if self.page < 5:
                #手动对指定的url进行请求发送
                count = self.page * 30
                new_url = format(self.url%count)
                self.page += 1
                # 手动对指定的url进行请求发送
                yield scrapy.Request(url=new_url,callback=self.parse)

    post请求发送和cookie的处理

    - post请求的发送:(麻烦少用,登陆用requests)
    - 重写父类的start_requests(self)方法
    - 在该方法内部只需要调用yield scrapy.FormRequest(url,callback,formdata)
    - cookie处理:scrapy默认情况下会自动进行cookie处理 

    #COOKIES_ENABLED = False 如果把注释取消就不会存储
    import scrapy
    
    
    class PostdemoSpider(scrapy.Spider):
        name = 'postDemo'
        # allowed_domains = ['www.xxx.com']
        #https://fanyi.baidu.com/sug
        start_urls = ['https://fanyi.baidu.com/sug']
        #父类方法,就是将start_urls中的列表元素进行get请求的发送
        # def start_requests(self):
        #     for url in self.start_urls:
        #         yield scrapy.Request(url=url,callback=self.parse)
    
        def start_requests(self):
            for url in self.start_urls:
                data = {
                    'kw':'cat'
                }
                #post请求的手动发送使用的是FormRequest
                yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)
    
        def parse(self, response):
            print(response.text)

    请求传参:

    - 使用场景:如果使用scrapy爬取的数据没有在同一张页面中,则必须使用请求传参

            - 基于起始url进行数据解析(parse)
                - 解析数据
                    - 电影的名称
                    - 详情页的url
                    - 对详情页的url发起手动请求(指定的回调函数parse_detail),进行请求传参(meta)
                        meta传递给parse_detail这个回调函数
                    - 封装一个其他页码对应url的一个通用的URL模板
                    - 在for循环外部,手动对其他页的url进行手动请求发送(需要指定回调函数==》parse)
                - 定义parse_detail回调方法,在其内部对电影的简介进行解析。解析完毕后,需要将解析到的电影名称
                    和电影的简介封装到同一个item中。
                    - 接收传递过来的item,并且将解析到的数据存储到item中,将item提交给管道

    5大核心组键

    spider对url进行请求对象封装 传给引擎,转发给调度器

    调度器有 队列,过滤器2个部分,将过滤请求加入到队列中

    调度器在把返回的请求通过引擎 给下载器

    下载器在上互联网上进行下载,互联网response给下载器

    下载器给引擎,引擎给splder,产生item再给引擎 再给管道

    引擎作用:解析所有数据,事物触发

     =====请求传参抓前5页内容

    # -*- coding: utf-8 -*-
    import scrapy
    from ..items import MovieproItem
    
    class MovieSpider(scrapy.Spider):
        name = 'movie'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.4567tv.tv/frim/index1.html']
    
        #通用的url模板只适用于非第一页
        url = 'https://www.4567tv.tv/frim/index1-%d.html'
        page = 2
    
        #电影名称(首页),简介(详情页)
        def parse(self, response):
            li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
            for li in li_list:
                name = li.xpath('./div/a/@title').extract_first()#电影名称
                detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()#电影详情url
                item = MovieproItem()
                item['name'] = name
    
                #对详情页的url发起get请求
                #请求传参:meta参数对应的字典就可以传递给请求对象中指定好的回调函数
                yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
            if self.page <= 5:
                new_url = format(self.url%self.page)#第二页
                self.page += 1
                yield scrapy.Request(url=new_url,callback=self.parse)#再次调page
        #解析详情页的页面数据
        def detail_parse(self,response):
            #回调函数内部通过response.meta就可以接收到请求传参传递过来的字典
            item = response.meta['item']
            desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
            item['desc'] = desc
            yield item

    ua伪装 代理ip

    下载中间件:批量拦截所有的请求和相应啊

    拦截请求: UA伪装 代理ip

     

    from scrapy import signals
    import random
    
    #批量拦截所有的请求和响应
    class MiddlewearproDownloaderMiddleware(object):
        #UA池
        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
            "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        ]
        #代理池
        PROXY_http = [
            '153.180.102.104:80',
            '195.208.131.189:56055',
        ]
        PROXY_https = [
            '120.83.49.90:9000',
            '95.189.112.214:35508',
        ]
    
        #拦截正常请求:request就是该方法拦截到的请求,spider就是爬虫类实例化的一个对象
        def process_request(self, request, spider):
            print('this is process_request!!!')
            #UA伪装
            request.headers['User-Agent'] = random.choice(self.user_agent_list)
            return None
    
        #拦截所有的响应
        def process_response(self, request, response, spider):
    
            return response
    
        #拦截发生异常的请求对象
        def process_exception(self, request, exception, spider):
            print('this is process_exception!!!!')
            #代理ip的设定
            if request.url.split(':')[0] == 'http':
                request.meta['proxy'] = random.choice(self.PROXY_http)
            else:
                request.meta['proxy'] = random.choice(self.PROXY_https)
    
            #将修正后的请求对象重新进行请求发送
            return request

    scrapy+selenium 进行相应篡改

    (慢死 有时候还不对)个人评价

    scrapy中应用selenium的编码流程:
        - 爬虫类中定义一个属性bro
        - 爬虫类中重写父类的一个方法closed,在该方法中关闭bro
        - 在中间件类的process_response中编写selenium自动化的相关操作

    wangyi163.py

    import scrapy
    
    from ..items import WangyiItem
    from selenium import webdriver
    
    class Wangye163Spider(scrapy.Spider):
        name = 'wangye163'
        # allowed_domains = ['www.163.com']
        start_urls = ['https://news.163.com/']
        # 浏览器实例化的操作只会被执行一次
        bro = webdriver.Chrome(executable_path='D:老男孩老师笔记第六阶段爬虫pachong练习day06wangyiwangyispiderschromedriver.exe')
    
        urls = []  # 5大板块对应的url 发送给中间件
    
        def parse(self, response):
            li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
            for index in [3, 4, 6, 7, 8]:
                li = li_list[index]  # 查找第几个位子
                new_url = li.xpath('./a/@href').extract_first()
                print(new_url,1111)
                self.urls.append(new_url)
                # 五大板块对应的url进行请求发送
                yield scrapy.Request(url=new_url, callback=self.parse_news)
    
        # 用来解析每一个板块对应的新闻数据(新闻标题)
        def parse_news(self, response):
            div_list = response.xpath('//div[@class="ndi_main"]/div')
            for div in div_list:
                try:
                    title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
                except:
                    continue
                new_detail_url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
                # 实例化item对象将解析内容存储到item对象中
                item = WangyiItem()
                item['title'] = title
                # 对详情页的url手动请求发送以便回去新闻内容   content在另一个解析对象中,所以要进行传参
                yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item': item})
    
        def parse_detail(self, response):  # 解析方法
            item = response.meta['item']  # 这时候就可以拿到item对象了
    
            # 通过response解析出新闻内容
            content = response.xpath('//div[@id="endText"]/p//text()').extract()
            if not content:
                content = response.xpath('//div[@class="viewport"]/div[@class="overview"]/p/text()').extract_first()
            # else:
            #     content = response.xpath('//div[@id="endText"]//text()').extract()
            content = ''.join(content).strip()
            item['content'] = content
            yield item
    
        def closed(self, spider):  # 重写父类的关闭方案法
            print('爬虫系统结束')
            self.bro.quit()

    中间件

    from scrapy import signals
    from scrapy.http import HtmlResponse
    from time import sleep
    
    class WangyiDownloaderMiddleware(object):
        def process_request(self, request, spider):
            return None
            #在5五大板块中的url 如果是拦击response
        def process_response(self, request, response, spider):
            if request.url in spider.urls:  # 爬虫类中spider对赢得urls属性
                # 就要对其对应的相应对象进行处理
    
                # 获取了在爬中类中定义好的浏览器对象
                bro = spider.bro
                bro.get(request.url)  # 获取携带了新闻数据页面源码数据
                '''
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                sleep(1)
                '''
    
                page_text = bro.page_source  # 实例化一个新的响应对象 age_text是符合要求的里面包含了动态数据
                new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
                #满足条件的response
                return new_response
            else:
                return response
    
        def process_exception(self, request, exception, spider):
            pass
    
        def spider_opened(self, spider):
            spider.logger.info('Spider opened: %s' % spider.name)

     CrawlSpider

    作用:就是用于进行全站数据的爬取
    - CrawlSpider就是Spider的一个子类

    - 如何新建一个基于CrawlSpider的爬虫文件
    - scrapy genspider -t crawl xxx www.xxx.com

    - LinkExtractor连接提取器:根据指定规则(正则)进行连接的提取

    - Rule规则解析器:将链接提取器提取到的链接进行请求发送,然后对获取的页面数据进行指定规则(callback)的解析

    - 一个链接提取器对应唯一一个规则解析器

    dianyin

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from ..items import SunlineproItem,ContentItem
    
    class DianyiSpider(CrawlSpider):
        name = 'dianyi'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
    
        link= LinkExtractor(allow=r'type=4&page=d+')#提取页码连接
    
        link1 = LinkExtractor(allow=r'question/2019d+/d+.shtml')  # 提取详情页的链接
    
        rules = (
            Rule(link, callback='parse_item', follow=False),#等于true就会自动爬取所有页
            Rule(link1, callback='parse_detail'),
        )
        #解析出标题和网友名称
        def parse_item(self, response):
            tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
            for tr in tr_list:
                title = tr.xpath('./td[2]/a[2]/text()').extract_first()
                netFriend = tr.xpath('./td[4]/text()').extract_first()
                item = SunlineproItem()#没有办法进行传参请求 所以弄了2个item
                item['title'] = title
                item['netFriend'] = netFriend
    
                yield item
        #解析出新闻的内容
        def parse_detail(self,response):
            content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/text()').extract()
            content = ''.join(content)
            print(content,3333333333)
            item = ContentItem()
            item['content'] = content
    
            yield item

    items

    import scrapy
    class SunlineproItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title=scrapy.Field()
        netFriend = scrapy.Field()
    
    class ContentItem(scrapy.Item):
        # define the fields for your item here like:
        content = scrapy.Field()

    pipelines

    class SunlineproPipeline(object):
        def process_item(self, item, spider):
            #接收到的item究竟是什么类型的
            if item.__class__.__name__ =='SunlineproItem':#返回当前对象的类名
                print(item['title'],item['netFriend'])
            else:
                print(item['content'])#不是就取出详情
            return item

    settings中打开相关 设置

    分布式爬取

    - 概念:可以将一组程序执行在多台机器上(分布式机群),使其进行数据的分布爬取。
    - 原生的scrapy框架是否可以实现分布式?
    - 不可以?
    - 调度器不可以被分布式机群共享
    - 管道不可以被分布式机群共享
    - 借助scrapy-redis这个模块帮助scrapy实现分布式
    - scrapy-redis作用:
    - 可以提供可以被共享的管道和调度器
    - pip install scrapy-redis

    - 分布式的实现流程
    - 导报:from scrapy_redis.spiders import RedisCrawlSpider
    - 修改爬虫文件的代码:
    - 将当前爬虫类的父类修改成RedisCrawlSpider
    - 将start_urls删除
    - 添加一个新属性redis_key = 'ts':可以被共享的调度器中的队列名称

    - 设置管道:
    ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 400
    # 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
    }
    - 设置调度器:
    # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis组件自己的调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
    SCHEDULER_PERSIST = True

    - 指定redis服务器
    REDIS_HOST = '192.168.12.154'
    REDIS_PORT = 6379

    - 配置redis:
    修改Redis的配置文件:redis.windows.conf
    - #bind 127.0.0.1
    - protected-mode no
    - 携带配置文件启动redis服务
    - redis-server ./redis.windows.conf
    - 启动redis客户端

    - 执行工程:scrapy runspider xxx.py

    - 手动将起始url扔入调度器的队列中(redis-cli):lpush ts www.xxx.com

    - redis-cli: items:xxx

    ###############

    scrapy startproject scrapyRedisPro

    scrapy genspider -t crawl test www.x.com

    ========test.py======

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from scrapyRedisPro.items import ScrapyredisproItem
    from scrapy_redis.spiders import RedisCrawlSpider
    from scrapy_redis.spiders import RedisSpider
    
    class TestSpider(RedisCrawlSpider):
        name = 'test'
        # allowed_domains = ['www.xxx.com']
        # start_urls = ['http://www.xxx.com/']
        redis_key = 'ts' #可以被共享的调度器中的队列名称
        rules = (
            Rule(LinkExtractor(allow=r'type=4&page=d+'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
            for tr in tr_list:
                title = tr.xpath('./td[2]/a[2]/text()').extract_first()
                netFriend = tr.xpath('./td[4]/text()').extract_first()
                item = ScrapyredisproItem()
                item['title'] = title
                item['net'] = netFriend
    
                yield item
                #提交的item必须保证提交到可以被共享的管道中

    =====items=====

    import scrapy
    
    
    class ScrapyredisproItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title = scrapy.Field()
        net = scrapy.Field()

    ====settings====

    ITEM_PIPELINES = {
       # 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
        'scrapy_redis.pipelines.RedisPipeline': 400
    }
    # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    # 使用scrapy-redis组件自己的调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
    SCHEDULER_PERSIST = True
    
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
    
    CONCURRENT_REQUESTS = 2#请求进程

    D:老男孩老师笔记第六阶段爬虫pachong练习day07scrapyRedisProscrapyRedisProspiders>scrapy runspider test.py #开启

    C:UsersAdministrator>redis-cli
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> ^C
    C:UsersAdministrator>redis-cli -h 127.0.0.1
    127.0.0.1:6379> lpush ts http://wz.sun0769.com/index.php/question/questionType?type=4&page=

    127.0.0.1:6379> lrange test:items 0 -1

    url增量爬虫

    - 概念:监测网上数据更新的情况。
    - 数据指纹

    判断网站详情页的url是否进行请求发送

    ########dianying.py#############

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from ..items import ScrapydianyingItem
    from redis import Redis
    
    
    class DianyingSpider(CrawlSpider):
        name = 'dianying'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.4567tv.tv/frim/index1.html']
        link = LinkExtractor(allow=r'/frim/index1-d+.html')
        rules = (
            Rule(link, callback='parse_item', follow=False),#只爬取前几页
        )
        conn = Redis(host='127.0.0.1', port=6379)
    
        # 解析电影的名称和详情页的url
        def parse_item(self, response):
            li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
            for li in li_list:
                title = li.xpath('./div/a/@title').extract_first()
                detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first()
                item = ScrapydianyingItem()
                item['title'] = title
    
                # 判断该详情页的url是否进行请求发送
                ex = self.conn.sadd('movie_detail_urls', detail_url)
                if ex == 1:  # 说明detail_url不存在于redis的set中
                    print('已有最新数据更新,请爬......')
                    yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
                else:
                    print('暂无新数据的更新!!!')
    
        def parse_detail(self, response):
            item = response.meta['item']
            desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
            item['desc'] = desc
    
            yield item

    ########items############

    class ScrapydianyingItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title=scrapy.Field()
        desc=scrapy.Field()

    ###########pipelines##########

    class ScrapydianyingPipeline(object):
        def process_item(self, item, spider):
            dic = {
                'title':item['title'],
                'desc':item['desc']
            }
            conn = spider.conn
    
            conn.lpush('movie_data',dic)
            return item

    ##客户端连接redis进行验证

    127.0.0.1:6379> keys *
    1) "movie_detail_urls"
    2) "movie_data"

    127.0.0.1:6379> llen movie_data
    (integer) 144

    127.0.0.1:6379> smembers movie_detail_urls

    数据增量爬虫

    - 需求:爬取糗事百科中的段子和作者数据。

    #############qiubai.py#############

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from redis import Redis
    import hashlib
    from ..items import BaikeItem
    class QiubaiSpider(CrawlSpider):
        name = 'qiubai'
        # allowed_domains = ['www.baid.com']
        start_urls = ['https://www.qiushibaike.com/text/']
    
        rules = (
            Rule(LinkExtractor(allow=r'/text/page/d+/'), callback='parse_item', follow=False),
        )
        # 创建redis链接对象
        conn = Redis(host='127.0.0.1', port=6379)
        def parse_item(self, response):
            div_list = response.xpath('//div[@id="content-left"]/div')
            for div in div_list:
                item = BaikeItem()
                item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
                item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first()
                # 将解析到的数据值生成一个唯一的标识进行redis存储
                source = item['author'] + item['content']
                source_id = hashlib.sha256(source.encode()).hexdigest()
                # 将解析内容的唯一表示存储到redis的data_id中
                ex = self.conn.sadd('data_id', source_id)
                if ex == 1:
                    print('该条数据没有爬取过,可以爬取......')
                    yield item
                else:
                    print('该条数据已经爬取过了,不需要再次爬取了!!!')

    ###########items.py###########

    import scrapy
    
    
    class BaikeItem(scrapy.Item):
        # define the fields for your item here like:
        author=scrapy.Field()
        content=scrapy.Field()

    #########pipelines.py#############

    class BaikePipeline(object):
        def process_item(self, item, spider):
            dic = {
                'title':item['author'],
                'desc':item['content']
            }
            conn = spider.conn
    
            conn.lpush('qiubaiData',dic)
            return item

    D:老男孩老师笔记第六阶段爬虫pachong练习day07aike>scrapy crawl qiubai 执行

  • 相关阅读:
    第7次实践作业 25组
    第6次实践作业 25组
    第5次实践作业
    第4次实践作业
    第3次实践作业
    第2次实践作业
    第1次实践作业
    软工实践个人总结
    2019 SDN大作业
    C语言Il作业01
  • 原文地址:https://www.cnblogs.com/zaizai1573/p/10951746.html
Copyright © 2011-2022 走看看