zoukankan      html  css  js  c++  java
  • Python网络爬虫 第三章 requests进阶

    我们在之前的爬⾍中其实已经使⽤过headers了。 header为HTTP协议中的请求头. ⼀般存放⼀些和请求内容⽆关的数据,有时也会存放⼀些安全验证信息.⽐如常⻅的User-Agent, token, cookie等。
    通过requests发送的请求, 我们可以把请求头信息放在headers中, 也可以单独进⾏存放, 最终由requests⾃动帮我们拼接成完整的http请求头。

    一、处理cookie模拟登录

    这里以17k小说网为例https://www.17k.com/

    我们的目的是先去登录,得到用户信息的cookie,带着cookie 去请求到书架url,获得书架上的内容。这个过程中使用了一个新的概念session会话

    可以直接理解成session是一连串的请求. 在这个过程中的cookie不会丢失。

    1、登录

    利用F12获取到登录需要的用户名密码来源。

    Request URL: https://passport.17k.com/ck/user/login

    post方法

    2、拿书架上的数据

    Request URL:
    https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919

    3、代码

    import requests
    
    # 会话
    session = requests.session()
    data = {
        "loginName": "18614075987",
        "password": "q6035945"
    }
    
    # 1. 登录
    url = "https://passport.17k.com/ck/user/login"
    session.post(url, data=data)
    # resp = session.post(url, data=data)
    # print(resp.text)
    # print(resp.cookies)  # 看cookie
    #
    # # 2. 拿书架上的数据
    # # 刚才的那个session中是有cookie的
    resp = session.get('https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919')
    
    print(resp.json())

    二、防盗链的处理

     我们日常访问网页,如果从一个网页跳转到另一个网页,http 头字段里面会带个 Referer的参数。那么图片服务器通过检测 Referer 是否来自指定域名,来进行防盗链。
    简单点说,服务器会判断你是否从规定的域名来访问图片或者视频,如果是那么就正常显示,不是的话,会跳转到别的地方,那么我们用爬虫所采集的图片就会出现问题!

    这里我们以梨视频网站为例讲解一下,其中一个视频的URL链接为https://www.pearvideo.com/video_1728542

    利用F12可以找到视频的播放源,但是直接查看网页源代码却找不到,可以推断视频资源是通过后期JS生成出来的。

     查看一下XHR,预览后发现这样一个链接https://video.pearvideo.com/mp4/adshort/20210503/1620027759929-15669083_adpkg-ad_hd.mp4

     但是却发现打不开这个链接?

    对比一下,发现只有cont那部分内容不同,猜测应该进行了替换,所以我们只需要获取cont部分拼接一下,就能获得播放视频的链接了。cont应该就是梨视频的视频编号,我们一开始进去的链接上就含有这个编号video_1728542

     到现在可以梳理一下整个过程

    • 1. 拿到contId
    • 2. 拿到videoStatus返回的json. ->  srcURL
    • 3. srcURL里面的内容进行修整
    • 4. 下载视频
    import requests
    
    # 拉取视频的网址
    url = "https://www.pearvideo.com/video_1728542"
    contId = url.split("_")[1]
    
    videoStatusUrl = f"https://www.pearvideo.com/videoStatus.jsp?contId={contId}"
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36",
        # 防盗链: 溯源, 当前本次请求的上一级是谁
        "Referer": url
    }
    
    resp = requests.get(videoStatusUrl, headers=headers)
    dic = resp.json()
    
    srcUrl = dic['videoInfo']['videos']['srcUrl']
    systemTime = dic['systemTime']
    srcUrl = srcUrl.replace(systemTime, f"cont-{contId}")
    
    # 下载视频
    with open("a.mp4", mode="wb") as f:
        f.write(requests.get(srcUrl).content)

    三、代理

    当我们反复抓取⼀个⽹站时, 由于请求过于频繁, 服务器很可能会将你的IP进⾏封锁来反爬. 应对⽅案就是通过⽹络代理的形式进⾏伪装.
    代理的原理:


    从图上可以得知. 对于⽬标⽹站来说. 是通过代理服务器发送的请求.也就可以避免你的IP被封锁了.
    爬⾍如何使⽤代理

    # 原理. 通过第三方的一个机器去发送请求
    import requests
    
    
    # 218.60.8.83:3129
    proxies = {
        "https": "https://218.60.8.83:3129"
    }
    
    resp = requests.get("https://www.baidu.com", proxies=proxies)
    resp.encoding = 'utf-8'
    print(resp.text)

    四、综合训练 抓取⽹易云⾳乐评论信息

    以我最喜欢的歌手许嵩的《雅俗共赏》为例

    https://music.163.com/#/song?id=411214279

    先准备好

    pip install pycryptodome

    但是请求的参数是一堆完全看不懂的信息,似乎是被加密过?

    至此,我们的需求是

    • 1. 找到未加密的参数                    
    • 2. 想办法把参数进行加密(必须参考网易的逻辑),params  encSecKey
    • 3. 请求到网易. 拿到评论信息

     这里先通过调用栈,查看一下有哪些js脚本执行过

     网易云音乐评论的URL https://music.163.com/weapi/comment/resource/comments/get?csrf_token=

     注意到这里参数依旧是被加密的,我们需要向前找找到底是在哪里参数开始被加密了。调用Call Stack查看一下。

     基本可以断定是在这里被加密了

     一步步调试,参数和加密过程基本可以确定

     请求参数:

    data = {
        "csrf_token": "",
        "cursor": "-1",
        "offset": "0",
        "orderType": "1",
        "pageNo": "1",
        "pageSize": "20",
        "rid": "R_SO_4_411214279",
        "threadId": "R_SO_4_411214279"
    }

    加密过程 直接搜索window.asrsea,入口是d

     加密过程

    !function() {
        function a(a) {
            var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
            for (d = 0; a > d; d += 1)
                e = Math.random() * b.length,
                e = Math.floor(e),
                c += b.charAt(e);
            return c
        }
        function b(a, b) {
            var c = CryptoJS.enc.Utf8.parse(b)
              , d = CryptoJS.enc.Utf8.parse("0102030405060708")
              , e = CryptoJS.enc.Utf8.parse(a)
              , f = CryptoJS.AES.encrypt(e, c, {
                iv: d,
                mode: CryptoJS.mode.CBC
            });
            return f.toString()
        }
        function c(a, b, c) {
            var d, e;
            return setMaxDigits(131),
            d = new RSAKeyPair(b,"",c),
            e = encryptedString(d, a)
        }
        function d(d, e, f, g) {
            var h = {}
              , i = a(16);
            return h.encText = b(d, g),
            h.encText = b(h.encText, i),
            h.encSecKey = c(i, e, f),
            h
        }
        function e(a, b, d, e) {
            var f = {};
            return f.encText = c(a + e, b, d),
            f
        }
        window.asrsea = d,
        window.ecnonasr = e
    }();

    解析一下

      function a(a = 16) {  # 随机的16位字符串
            var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
            for (d = 0; a > d; d += 1)  # 循环16次
                e = Math.random() * b.length,  # 随机数 1.2345
                e = Math.floor(e),  # 取整  1 
                c += b.charAt(e);  # 去字符串中的xxx位置 b
            return c
        }
        function b(a, b) {  # a是要加密的内容, 
            var c = CryptoJS.enc.Utf8.parse(b) #  # b是秘钥
              , d = CryptoJS.enc.Utf8.parse("0102030405060708")
              , e = CryptoJS.enc.Utf8.parse(a)  # e是数据
              , f = CryptoJS.AES.encrypt(e, c, {  # c 加密的秘钥
                iv: d,  # 偏移量
                mode: CryptoJS.mode.CBC  # 模式: cbc
            });
            return f.toString()
        }
        function c(a, b, c) {   # c里面不产生随机数
            var d, e;
            return setMaxDigits(131),
            d = new RSAKeyPair(b,"",c),
            e = encryptedString(d, a)
        }
        function d(d, e, f, g) {  d: 数据,   e: 010001, f: 很长, g: 0CoJUm6Qyw8W8jud
            var h = {}  # 空对象
              , i = a(16);  # i就是一个16位的随机值, 把i设置成定值 
            h.encText = b(d, g)  # g秘钥
            h.encText = b(h.encText, i)  # 返回的就是params  i也是秘钥
            h.encSecKey = c(i, e, f)  # 得到的就是encSecKey, e和f是定死的 ,如果此时我把i固定, 得到的key一定是固定的
            return h
        }
        
        两次加密: 
        数据+g => b => 第一次加密+i => b = params

     最后的代码

    # 1. 找到未加密的参数                       # window.arsea(参数, xxxx,xxx,xxx)
    # 2. 想办法把参数进行加密(必须参考网易的逻辑), params  => encText, encSecKey => encSecKey
    # 3. 请求到网易. 拿到评论信息
    
    # 需要安装pycrypto:
    from Crypto.Cipher import AES
    from base64 import b64encode
    import requests
    import json
    
    
    url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
    
    # 请求方式是POST
    data = {
        "csrf_token": "",
        "cursor": "-1",
        "offset": "0",
        "orderType": "1",
        "pageNo": "1",
        "pageSize": "20",
        "rid": "R_SO_4_411214279",
        "threadId": "R_SO_4_411214279"
    }
    
    # 服务于d的
    f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
    g = "0CoJUm6Qyw8W8jud"
    e = "010001"
    i = "d5bpgMn9byrHNtAh"  # 手动固定的. -> 人家函数中是随机的
    
    def get_encSecKey():  # 由于i是固定的. 那么encSecText就是固定的.  c()函数的结果就是固定的
        return "1b5c4ad466aabcfb713940efed0c99a1030bce2456462c73d8383c60e751b069c24f82e60386186d4413e9d7f7a9c7cf89fb06e40e52f28b84b8786b476738a12b81ac60a3ff70e00b085c886a6600c012b61dbf418af84eb0be5b735988addafbd7221903c44d027b2696f1cd50c49917e515398bcc6080233c71142d226ebb"
    
    
    # 把参数进行加密
    def get_params(data):  # 默认这里接收到的是字符串
        first = enc_params(data, g)
        second = enc_params(first, i)
        return second  # 返回的就是params
    
    
    # 转化成16的倍数, 位下方的加密算法服务
    def to_16(data):
        pad = 16 - len(data) % 16
        data += chr(pad) * pad
        return data
    
    
    # 加密过程
    def enc_params(data, key):
        iv = "0102030405060708"
        data = to_16(data)
        aes = AES.new(key=key.encode("utf-8"), IV=iv.encode('utf-8'), mode=AES.MODE_CBC)  # 创建加密器
        bs = aes.encrypt(data.encode("utf-8"))  # 加密, 加密的内容的长度必须是16的倍数
        return str(b64encode(bs), "utf-8")  # 转化成字符串返回,
    
    
    # 处理加密过程
    """
        function a(a = 16) {  # 随机的16位字符串
            var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
            for (d = 0; a > d; d += 1)  # 循环16次
                e = Math.random() * b.length,  # 随机数 1.2345
                e = Math.floor(e),  # 取整  1 
                c += b.charAt(e);  # 去字符串中的xxx位置 b
            return c
        }
        function b(a, b) {  # a是要加密的内容, 
            var c = CryptoJS.enc.Utf8.parse(b) #  # b是秘钥
              , d = CryptoJS.enc.Utf8.parse("0102030405060708")
              , e = CryptoJS.enc.Utf8.parse(a)  # e是数据
              , f = CryptoJS.AES.encrypt(e, c, {  # c 加密的秘钥
                iv: d,  # 偏移量
                mode: CryptoJS.mode.CBC  # 模式: cbc
            });
            return f.toString()
        }
        function c(a, b, c) {   # c里面不产生随机数
            var d, e;
            return setMaxDigits(131),
            d = new RSAKeyPair(b,"",c),
            e = encryptedString(d, a)
        }
        function d(d, e, f, g) {  d: 数据,   e: 010001, f: 很长, g: 0CoJUm6Qyw8W8jud
            var h = {}  # 空对象
              , i = a(16);  # i就是一个16位的随机值, 把i设置成定值 
            h.encText = b(d, g)  # g秘钥
            h.encText = b(h.encText, i)  # 返回的就是params  i也是秘钥
            h.encSecKey = c(i, e, f)  # 得到的就是encSecKey, e和f是定死的 ,如果此时我把i固定, 得到的key一定是固定的
            return h
        }
        
        两次加密: 
        数据+g => b => 第一次加密+i => b = params
    """
    
    # 发送请求. 得到评论结果
    resp = requests.post(url, data={
        "params": get_params(json.dumps(data)),
        "encSecKey": get_encSecKey()
    })
    
    print(resp.text)

    代码倒还好,最难的是整个推导过程,涉及到了前端和密码学的知识,有点坑。。。

    作者:王陸

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

    个性签名:罔谈彼短,靡持己长。做一个谦逊爱学的人!

    本站使用「署名 4.0 国际」创作共享协议,转载请在文章明显位置注明作者及出处。鉴于博主处于考研复习期间,有什么问题请在评论区中提出,博主尽可能当天回复,加微信好友请注明原因

  • 相关阅读:
    中断和异常
    MATLAB总结二
    关于在写第一个模式识别大作业中遇到的几个问题的总结
    学习QT——GUI的基础用法(2)
    Matlab练习——rpy2tr函数与自己实现的ZYX欧拉角的结果不同的问题
    PHP之文件的锁定、上传与下载
    PHP之文件目录基础操作
    PHP之会话控制小结
    PHP之curl
    PHP之数组函数归类
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/14727527.html
Copyright © 2011-2022 走看看