requests高级

视频数据爬取

  • url:https://www.51miz.com/shipin/

    • 爬取当前url页面中营销日期下的几个视频数据。
  • 找寻每个视频的播放地址:

    • 通过观察视频详情页的页面数据,并没有发现视频的播放地址,只有一张播放图片。
    • 打开抓包工具,点击页面的播放按钮,找到了视频的播放数据包,可以提取出视频的播放地址,地址格式为:
    https://video-js.51miz.com/preview/video/00/00/11/63/V-116374-7B5E698E.mp4
    
    • 观察其他视频的播放地址,找寻规律:
    相同之处:https://video-js.51miz.com/preview/video/ 
    不同之处:00/00/11/63/V-116374-7B5E698E.mp4
    
    • 在首页url页面数据中,找到video标签(对应每个视频),标签的poster属性值中出现了视频播放地址中的(不同之处)相似部分,但是经过测试,不可用。
      • 在video标签下面有一个source标签,其内部的src属性值正好就是视频的播放地址 (补充上https:即可) 。
    import requests
    from lxml import etree
    headers = {
        'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
    }
    url = 'https://www.51miz.com/shipin/'
    response = requests.get(url=url,headers=headers)
    page_text = response.text
    
    #数据解析
    tree = etree.HTML(page_text)
    div_list = tree.xpath('/html/body/div[2]/div[2]/div[1]/div[2]/div[2]/div')
    for div in div_list:
        src_list = div.xpath('./a/div/div/div/video/source/@src')
        #要给视频地址进行补全
        for src in src_list:
            #src就是一个完整的视频地址
            src = 'https:'+src
            video_data = requests.get(url=src,headers=headers).content
            video_title = src.split('/')[-1]
            with open(video_title,'wb') as fp:
                fp.write(video_data)
            print(video_title,'爬取保存成功!')
        break
    

还可以这样写代码

import requests
from lxml import etree
import os
"""
通过分析,视频的url在首页,不在详情页,详情页的url是动态加载的,爬虫在详情页是获取不到的。
"""
url = 'https://www.51miz.com/shipin/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
response = requests.get(url=url, headers=headers).text  # ValueError: can only parse strings报错要加.text,获取首页源码数据。
# 解析源码数据,获取类url
tree = etree.HTML(response)  # 创建etree解析对象
div_list = tree.xpath('/html/body/div[2]/div[2]/div[1]/div[2]/div[2]/div')  # 调用etree工具的xpath函数定位各个类节点,返回一个列表。

for div in div_list:  # 遍历列表,取出单个分类节点div。
    # 调用xpath定位单个分类节点div,获取分类title和分类的url
    src_list = div.xpath('./a/div/div/div')  # 定位在包括视频标题和url的节点。
    for src in src_list:
        species_url = src.xpath('./video/source/@src')[0]  # 拿到source的属性值src,是字符串类型数据。加[0]解析出视频url
        url = 'https:' + species_url  # 补全视频url
        video = requests.get(url=url, headers=headers).content  # 发请求,获取源码数据,加.content转为二进制数据
        title = src.xpath('./div/text()')[0] + '——' + species_url.split('-')[-1]  # 视频标题。src.xpath('./div/text()')[0]获取目标节点下的文本数据并下标取值。species_url.split('-')[-1]把species_url中的值按-分割并下标取值最后一段数据。
        directory = '../pydirectory/觅知网/'
        # 检查目录是否存在,如果不存在则创建它,如果目录已存在,则直接存储文件。
        if not os.path.exists(directory):
            os.makedirs(directory)  # 使用 os.makedirs() 函数来创建目录
        file_path = os.path.join(directory, title)  # 使用 os.path.join() 函数来拼接文件路径。这样可以确保目录存在,并且文件可以正确地保存在指定的路径中。
        with open(file_path, 'wb') as fp:
            fp.write(video)
        print(file_path, '爬取成功')

        """
        在当前脚本所在位置创建文件夹可以写下面代码:
        directory = '../pydirectory/'
        检查目录是否存在,如果不存在则创建它,如果目录已存在,则不会进行任何操作。
        if not os.path.exists(directory):
            os.makedirs(directory)  # 使用 os.makedirs() 函数来创建目录
        file_path = os.path.join(directory, menu_title + '.html')  # 使用 os.path.join() 函数来拼接文件路径。这样可以确保目录存在,并且文件可以正确地保存在指定的路径中。
        with open(file_path, 'w', encoding='utf-8') as fp:
            fp.write(page_text)
        """
    # break

  • 什么是cookie?

    • cookie的本质就是一组数据(键值对的形式存在)
    • 是由服务器创建,返回给客户端,最终会保存在客户端浏览器中。
    • 如果客户端保存了cookie,则下次再次访问该服务器,就会携带cookie进行网络访问。
  • 典型的案例:网站的免密登录

  • 爬取雪球网中的咨询数据

    • url:https://xueqiu.com/,需求就是爬取热帖内容

    • 经过分析发现帖子的内容是通过ajax动态加载出来的,因此通过抓包工具,定位到ajax请求的数据包,从数据包中提取:

    • import requests
      import os
      headers = {
          'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
      }
      url = 'https://xueqiu.com/statuses/hot/listV2.json'
      param = {
          "since_id": "-1",
          "max_id": "311519",
          "size": "15",
      }
      response = requests.get(url=url,headers=headers,params=param)
      data = response.json()
      print(data)
      #发现没有拿到我们想要的数据
      
    • 分析why?

      • 切记:只要爬虫拿不到你想要的数据,唯一的原因是爬虫程序模拟浏览器的力度不够!一般来讲,模拟的力度重点放置在请求头中!
      • 上述案例,只需要在请求头headers中添加cookie即可!
    • 爬虫中cookie的处理方式(两种方式):

      • 手动处理:将抓包工具中的cookie复制到headers中即可

        • 缺点:

          • cookie通常都会存在有效时长,需要重复操作复制cookie
      • 自动处理

        基于session(绘画)对象实现自动处理cookie。

        • 1.创建一个空白的session对象。
        • 2.需要使用session对象发起请求,请求的目的是为了捕获cookie
          • 注意:如果session对象在发请求的过程中,服务器端产生了cookie,则cookie会自动存储在session对象中。
        • 3.使用携带cookie的session对象,对目的网址发起请求,就可以实现携带cookie的请求发送,从而获取想要的数据。

        注意:session对象至少需要发起两次请求

        • 第一次请求的目的是为了捕获存储cookie到session对象

        • 后次的请求,就是携带cookie发起的请求了

        • 缺点:

          • 编写麻烦(涉及逆向)
        # # 案例二(session的设置和运用)
        # 爬取雪球网中的咨询数据
        # 打开抓包工具->在页面向下滚动滚轮,加载热帖->在抓包界面出现listV2.json格式的数据-点击listV2.json格式数据进入预览界面->itmes->original_status->description(文本数据)->标头->常规->请求网址url->复制headers(请求标头)数据(Cookie,User-Agent)->载荷->复制params(参数)数据。
        # 请求网址url:https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=553502&size=15,这个url是带了请求参数的,问号后面的是请求参数,url里去掉,封装在字典param里设置为动态请求参数。
        
        '''
        url = 'https://xueqiu.com/statuses/hot/listV2.json'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
            'Cookie': 
        }
        # 经过测试,请求参数的值变与不变都不影响数据的获取,故没有设置为动态参数
        param = {
            # 如果遇到动态变化的请求参数,必须经过测试才能知道需不需要处理。
            'since_id': '-1',
            'max_id': '553502',  # 动态变化的请求参数
            'size': '15'  # size表示一次请求15条数据。
        }
        """
        如何测试请求参数是不是动态:
        进入抓包工具(Network网络)->刷新网页->找到格式为listV2.json格式的数据条,点击进去->载荷->复制param并对比,看里面的参数有无变化,有变化是动态参数,无变化是静态参数。
        """
        response = requests.get(url=url, headers=headers, params=param).json()
        print(response)  # 出现这样的报错:{'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试', 'error_uri': '/statuses/hot/listV2.json', 'error_data': None, 'error_code': '400016'},需要处理请求标头,尤其要注意加上User-Agent,Referer,Cookie等参数。
        '''
        
        # 上面的代码可以优化为:
        url = 'https://xueqiu.com/statuses/hot/listV2.json'  # 目标url
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
        }
        # 经过测试,请求参数的值变与不变都不影响数据的获取,故没有设置为动态参数
        param = {
            # 如果遇到动态变化的请求参数,必须经过测试才能知道需不需要处理。
            'since_id': '-1',
            'max_id': '553502',  # 动态变化的请求参数
            'size': '15'  # size表示一次请求15条数据。
        }
        # cookie是从什么时候创建的?是客户端第一次向服务器端发请求的时候,服务器端就向客户端创建并发送(响应)了cookie,客户端第二次发请求时就带上了cookie。
        # session对象会实时保存跟踪服务器端给客户端创建的cookie
        # 创建一个session对象
        session = requests.Session()  # 这是空白的session对象
        first_url = 'https://xueqiu.com/'  # 首页url
        # 使用session对象对首页url进行请求发送,目的获取服务器发送的cookie。如果该次请求时,服务器端给客户端创建cookie的话,则该cookie就会被保存在seesion对象中。
        session.get(url=first_url,
                    headers=headers)  # 这是session第一次发送请求(向首页发送,也只能向首页发送,否则获取不到cookie),目的获取cookie,所获cookie保存在当前的session对象中。后续就可以用保存了cookie的session对目标网页发请求获取所需数据。
        # 使用保存了cookie的session对象进行后续请求发送
        ret = session.get(url=url, headers=headers, params=param).json()  # 这是携带了动态cookie的session第二此发送请求(向目标页),目的获取有效数据。
        """
        如何判断请求后获取的数据用.json()还是用.text处理?最直接的方法就是看数据从哪获取到的,从元素中获取,就用.text,并进一步用xpath解析获取;从网络->响应处获取,就直接.json解析即可。还可以分别测试,如果用.json报错说response是str(字符串格式),就用.text。再就是看响应标头->Content-Type(响应数据类型):application/json;charset=UTF-8->application/json表示返回的是json格式的字符串,就用.json。json格式的字符串本质上还是字符串,但在某些数据处理方法上还是和纯字符串的处理方法不一样。json数据一般都是用Ajax加载出来的,找json数据也可以用Ajax抓取。
        """
        print(ret)
        """
        有些网页比较变态,就是不同层级网页的请求需要的cookie是不同的,需要将前面请求所获的cookie和后续所获cookie进行累加结合。cookie的本质是键值对。本质上还是通过给session添加新的键值对,就涉及到用逆向的知识解决问题了。处理涉及到cookie的反爬一定会用到session。可以手动往session里添加或删除键值对。
        """
        

模拟登录

  • 登录的目的有两个:
    • 一是验证用户名和密码是否正确
    • 二是让服务器端记住登录的状态,就用到cookie。cookie可以实现让服务器端接收或记住客户端相关的状态。
  • 获取https://passport.17k.com/中的书架页面里的图书信息
# 案例三
# 模拟登录(获取https://passport.17k.com/中的书架页面里的图书信息)
# 清理cookie:打开网页->F12->Application(应用)->Storage(存储)->Cookie->https系列数据->清除所有cookie。
# 如何抓包cookie:打开登录页面->F12->清理cookie->网络(确保勾选了(Preserve log)保留日志)->登录(只有经过登录才能获取cookie,用requests.Session()获取)->Name(名称)->login(注册,登录)->Headers(标头)->常规(General)->Request URL(请求网址,爬虫第一次对它发请求,要复制此url)->Request Method(请求方法)->响应标头(Response Headers)->Set-cookie(创建/设置cookie)->Payload(载荷)->loginName和password(账号和密码):
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
login_url = 'https://passport.17k.com/ck/user/login'  # 第一次发请求的url。
# post请求用data设置参数。第一次发请求携带的参数,在载荷里复制。
data = {
    'loginName': '150********',
    'password': '************'
}
session = requests.Session()  # 创建一个空的session,用来存储获取到的cookie。
# 这次请求的目的是为了获取cookie将其保存到session对象中,并不需要获取的源码数据,所以在请求成功后不需要加.text或.json。
session.post(url=login_url, headers=headers, data=data)  # 第一次发请求。不需要加.text或.json。post请求用data参数,携带登录账号和密码。

# 携带cookie向数据请求发送,获取数据信息
# 书架信息是动态加载出来的,不能直接从元素获取,要进行全局搜索定位数据包。所以第二次发请求时,书架url的获取需要在书架页面进行全局搜索定位数据包(F12->网络->点击搜索按钮(小放大镜)->搜索框里输入书架页面中的任一作品名->点击刷新),在获取的数据包标头里的常规复制url。
book_url = 'https://user.17k.com/ck/author2/shelf?page=1&appKey=*********'
page_text = session.get(url=book_url, headers=headers).json()  # 爬虫第二次发请求,获取书架源码数据,要加.json()。
# 解析书架信息
print(page_text)

代理

  • 什么是代理

    • 代理服务器
  • 代理服务器的作用

  • 就是用来转发请求和响应 image

  • 在爬虫中为何需要使用代理?

    • 有些时候,需要对网站服务器发起高频的请求,网站的服务器会检测到这样的异常现象,则会讲请求对应机器的ip地址加入黑名单,则该ip再次发起的请求,网站服务器就不再受理,则我们就无法再次爬取该网站的数据。
    • 使用代理后,网站服务器接收到的请求,最终是由代理服务器发起,网站服务器通过请求获取的ip就是代理服务器的ip,并不是我们客户端本身的ip。
  • 代理的匿名度

    • 透明:网站的服务器知道你使用了代理,也知道你的真实ip
    • 匿名:网站服务器知道你使用了代理,但是无法获知你真实的ip
    • 高匿:网站服务器不知道你使用了代理,也不知道你的真实ip(推荐)
  • 代理的类型(重要)

    • http:该类型的代理服务器只可以转发http协议的请求
    • https:可以转发https协议的请求
  • 如何获取代理?

  • 如何使用代理?

    • 测试:访问如下网址,返回自己本机ip

    • import requests
      from lxml import etree
      headers = {
          'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
      }
      url = 'http://www.cip.cc/'
      
      page_text = requests.get(url,headers=headers).text
      tree = etree.HTML(page_text)
      text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0]
      print(text.split('\n')[0])
      
    • 使用代理发起请求,查看是否可以返回代理服务器的ip

    • import requests
      from lxml import etree
      headers = {
          'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
      }
      url = 'http://www.cip.cc/'
      
      page_text = requests.get(url,headers=headers,proxies={'http':'代理IP'}).text   # '代理IP'里填入代理IP
      tree = etree.HTML(page_text)
      text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0]
      print(text.split('\n')[0])
      
    • 深度测试:

    深度测试:

    # 案例:https://www.kuaidaili.com/free/inha
    
    import requests
    from lxml import etree
    import random
    import time
    import random
    #
    # headers = {
    #     'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
    # }
    #
    # # 先测试不用代理会不会返回数据。
    # for page in range(1, 51):
    #     print('正在爬取第%d页的ip数据......' % page)
    #     #生成不同页码对应的url
    #     url = 'https://www.kuaidaili.com/free/inha/%d/' % page
    #     page_text = requests.get(url=url, headers=headers).text  # 每一页的详情代码
    #     time.sleep(0.5)  # 每两次请求隔0.5秒,请求频率太高会被封IP的,不会返回数据。间隔时间长不会被拒绝(1秒以上)。
    #     tree = etree.HTML(page_text)
    #     ip = tree.xpath('//*[@id="list"]/div[1]/table/tbody/tr[1]/td[1]/text()')[0]   # td[1]/text()每一页的第一条IP数据。
    #     print(ip)
    ```
    

    • 对快代理进行n次请求,直到本机无法访问快代理为止(证明本机ip被快代理封掉了)

    • 构建一个代理池(封装了很多代理ip和端口的容器),用于数据的批量爬取

    • import requests
      from lxml import etree
      import random
      import time
      import random
      
      # 构建一个代理池(封装了很多代理ip和端口的容器),用于数据的批量爬取
      headers = {
          'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
      }
      
      #构建一个代理池(有很多不同的代理服务器)
      proxy_url = 'http://webapi.http.zhimacangku.com/getip?num=31&type=1&pro=&city=0&yys=0&port=1&time=1&ts=0&ys=0&cs=0&lb=1&sb=0&pb=4&mr=1&regions='  # 芝麻HTTP代理的代理服务器中'生成链接'并复制'复制链接'的url粘贴在proxy_url里。
      # 提取数量设置多一点,十几个到几十个,看请求的频率和数量的多少而定。
      page_text = requests.get(url=proxy_url,headers=headers).text  # 拿到芝麻HTTP代理页面源码数据,如***.49.***.54:4224
      # 把***.49.***.54:4224变成{'HTTP':'***.49.***.54:4224'}格式
      proxy_list = [] #代理池
      for ips in page_text.split('\n')[0:-1]:  # 用回车'\n'切分字符串。[0:-1]去掉最后一个数据(最后一个是空数据)。
          dic = {}  # 创建一个空字典。
          dic['https'] = ips.strip()  # strip去除首尾空格\r。dic['https'] = ips字典的键为https,值为ips。把***.49.***.54:4224变成{'HTTP':'***.49.***.54:4224'}
          proxy_list.append(dic)  # 把dic['https'] = ips.strip()生成的字典全部放进proxy_list = []里,生成代理池。代理池中的每一个字典都可以充当proxies的参数(代理)。
      # 以上步骤就构建好了代理池。后续代码可以基于代理池发送请求。用隧道代理可以直接用,但隧道代理更贵一些。
      
      for page in range(1, 51):
          print('正在爬取第%d页的ip数据......' % page)
          #生成不同页码对应的url
          url = 'https://www.kuaidaili.com/free/inha/%d/' % page
          page_text = requests.get(url=url, headers=headers,proxies=random.choice(proxy_list)).text  # random.choice随机选取的模块,proxy_list是代理池,是一个列表。random.choice(proxy_list)的意思是随机从proxy_list列表里取出一个元素(一个字典格式数据。代理,是由代理服务器网站生成的代理。)proxies是字典格式。
          time.sleep(0.5)
          tree = etree.HTML(page_text)
          ip = tree.xpath('//*[@id="list"]/div[1]/table/tbody/tr[1]/td[1]/text()')[0]
          print(ip)
      

验证码

  • 图鉴平台:http://www.ttshitu.com/ (推荐)

  • 使用流程:

    • 注册登录图鉴平台

    • 登录后,点击开发文档,提取识别的源代码

    • 模块(tujian.py)的封装:

    • import base64
      import json
      import requests
      # 一、图片文字类型(默认 3 数英混合):
      # 1 : 纯数字
      # 1001:纯数字2
      # 2 : 纯英文
      # 1002:纯英文2
      # 3 : 数英混合
      # 1003:数英混合2
      #  4 : 闪动GIF
      # 7 : 无感学习(独家)
      # 11 : 计算题
      # 1005:  快速计算题
      # 16 : 汉字
      # 32 : 通用文字识别(证件、单据)
      # 66:  问答题
      # 49 :recaptcha图片识别
      # 二、图片旋转角度类型:
      # 29 :  旋转类型
      #
      # 三、图片坐标点选类型:
      # 19 :  1个坐标
      # 20 :  3个坐标
      # 21 :  3 ~ 5个坐标
      # 22 :  5 ~ 8个坐标
      # 27 :  1 ~ 4个坐标
      # 48 : 轨迹类型
      #
      # 四、缺口识别
      # 18 : 缺口识别(需要2张图 一张目标图一张缺口图)
      # 33 : 单缺口识别(返回X轴坐标 只需要1张图)
      # 五、拼图识别
      # 53:拼图识别
      #函数实现忽略
      def base64_api(uname, pwd, img, typeid):
          with open(img, 'rb') as f:
              base64_data = base64.b64encode(f.read())
              b64 = base64_data.decode()
          data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
          result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
          if result['success']:
              return result["data"]["result"]
          else:
              return result["message"]
          return ""
      
      
      def getImgCodeText(imgPath,imgType):#直接返回验证码内容
          #imgPath:验证码图片地址
          #imgType:验证码图片类型
          result = base64_api(uname='**********', pwd='**********', img=imgPath, typeid=imgType)
          return result
      
      
      
    • 验证码图片识别操作

      • 识别本地的滑动验证和坐标点击验证
posted @ 2023-10-30 21:46  氨糖  阅读(19)  评论(0编辑  收藏  举报