资讯专栏INFORMATION COLUMN

Python爬虫神器pyppeteer,对 js 加密降维打击

Karuru / 2278人阅读

摘要:爬虫神器,对加密降维打击是对无头浏览器的封装。使用等其他无头浏览器的最大优势当然是对加密实行降维打击,完全无视加密手段,对于一些需要登录的应用,也可以模拟点击然后保存。请求过滤你的那一段页面自动下拉脚本

爬虫神器pyppeteer,对 js 加密降维打击

pyppeteer 是对无头浏览器 puppeteer的 Python 封装。无头浏览器广泛用于自动化测试,同时也是一种很好地爬虫思路。

使用 puppeteer(等其他无头浏览器)的最大优势当然是对 js 加密实行降维打击,完全无视 js 加密手段,对于一些需要登录的应用,也可以模拟点击然后保存 cookie。而很多时候前端的加密是爬虫最难攻克的一部分。当然puppeteer也有劣势,最大的劣势就是相比面向接口爬虫效率很低,就算是无头的chromium,那也会占用相当一部分内存。另外额外维护一个浏览器的启动、关闭也是一种负担。

这篇文章我们来写一个简单的 demo,爬取拼多多搜索页面的数据,最终的效果如下:

我们把所有 api 请求的原始数据保存下来:

示例 json 文件如下:

开发环境

python3.6+

最好是 python3.7,因为asyncio在 py3.7中加入了很好用的asyncio.run()方法。

安装pyppeteer

如果安装有问题请去看官方文档。

python3 -m pip install pyppeteer

安装 chromium

你懂的,天朝网络环境很复杂,如果要用pyppeteer自己绑定的chromium,半天都下载不下来,所以我们要手动安装,然后在程序里面指定executablePath

下载地址:www.chromium.org/getting-inv…

hello world

pyppeteer 的 hello world 程序是前往exmaple.com截个图:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch({
        # Windows 和 Linux 的目录不一样,情换成自己对应的executable文件地址
        "executablePath": "你下载的Chromium.app/Contents/MacOS/Chromium",
    })
    page = await browser.newPage()
    await page.goto("http://example.com")
    await page.screenshot({"path": "example.png"})
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())
pyppeteer 重要接口介绍 pyppeteer.launch

launch 浏览器,可以传入一个字典来配置几个options,比如:

browser = await pyppeteer.launch({
    "headless": False, # 关闭无头模式
    "devtools": True, # 打开 chromium 的 devtools
    "executablePath": "你下载的Chromium.app/Contents/MacOS/Chromiu",
    "args": [ 
        "--disable-extensions",
        "--hide-scrollbars",
        "--disable-bundled-ppapi-flash",
        "--mute-audio",
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-gpu",
    ],
    "dumpio": True,  
})

其中所有可选的args参数在这里:peter.sh/experiments…

dumpio的作用:把无头浏览器进程的 stderr 核 stdout pip 到主程序,也就是设置为 True 的话,chromium console 的输出就会在主程序中被打印出来。

注入 js 脚本

可以通过page.evaluate形式,例如:

await page.evaluate("""
    () =>{
        Object.defineProperties(navigator,{
            webdriver:{
            get: () => false
            }
        })
    }
""")
我们会看到这一步非常关键,因为puppeteer出于政策考虑(这个词用的不是很好,就是那个意思)会设置window.navigator.webdrivertrue,告诉网站我是一个 webdriver 驱动的浏览器。有些网站比较聪明(反爬措施做得比较好),就会通过这个来判断对方是不是爬虫程序。

这等价于在 devtools 里面输入那一段 js 代码。

还可以加载一个 js 文件:

await page.addScriptTag(path=path_to_your_js_file)

通过注入 js 脚本能完成很多很多有用的操作,比如自动下拉页面等。

截获 request 和 response
await page.setRequestInterception(True)
page.on("request", intercept_request)
page.on("response", intercept_response)

intercept_requestintercept_response相当于是注册的两个回调函数,在浏览器发出请求和获取到请求之前指向这两个函数。

比如可以这样禁止获取图片、多媒体资源和发起 websocket 请求:

async def intercept_request(req):
    """请求过滤"""
    if req.resourceType in ["image", "media", "eventsource", "websocket"]:
        await req.abort()
    else:
        await req.continue_()

然后每次获取到请求之后将内容打印出来(这里只打印了fetchxhr类型response 的内容):

async def intercept_response(res):
    resourceType = res.request.resourceType
    if resourceType in ["xhr", "fetch"]:
        resp = await res.text()
        print(resp)
大家在学python的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的Python学习扣qun:784758214,这里是python学习者聚集地

一共有哪些resourceType,pyppeteer文档里面有:

拼多多搜索爬虫 页面自动下拉

拼多多的搜索界面是一个无限下拉的页面,我们希望能够实现无限下拉页面,并且能够控制程序提前退出,不然一直下拉也不好,我们可能并不需要那么多数据。

js 脚本

async () => {
    await new Promise((resolve, reject) => {

        // 允许下滑的最大高度,防止那种可以无限下拉的页面无法结束
        const maxScrollHeight = null;

        // 控制下拉次数
        const maxScrollTimes = null;
        let currentScrollTimes = 0;

        // 记录上一次scrollHeight,便于判断此次下拉操作有没有成功,从而提前结束下拉
        let scrollHeight = 0;

        // maxTries : 有时候无法下拉可能是网速的原因
        let maxTries = 5;
        let tried = 0;

        const timer = setInterval(() => {

            // 下拉失败,提前退出
            // BUG : 如果网速慢的话,这一步会成立~
            // 所以设置一个 maxTried 变量
            if (document.body.scrollHeight === scrollHeight) {
                tried += 1;
                if (tried >= maxTries) {
                    console.log("reached the end, now finished!");
                    clearInterval(timer);
                    resolve();
                }
            }

            scrollHeight = document.body.scrollHeight;
            window.scrollTo(0, scrollHeight);
            window.scrollBy(0, -10);

            // 判断是否设置了maxScrollTimes
            if (maxScrollTimes) {
                if (currentScrollTimes >= maxScrollTimes) {
                    clearInterval(timer);
                    resolve();
                }
            }

            // 判断是否设置了maxScrollHeight
            if (maxScrollHeight) {
                if (scrollHeight >= maxScrollHeight) {
                    if (currentScrollTimes >= maxScrollTimes) {
                        clearInterval(timer);
                        resolve();
                    }
                }
            }

            currentScrollTimes += 1;
            // 还原 tried
            tried = 0;
        }, 1000);

    });
};

这里面有几个重要的参数:

interval : 下拉间隔时间,以毫秒为单位

maxScrollHeight : 运行页面下拉最大高度

maxScrollTimes : 最多下拉多少次(推荐使用,可以更好控制爬取多少数据)

maxTries : 下拉不成功时最多重试几次,比如有时候会因为网络原因导致没能在 interval ms 内成功下拉

把这些替换成你需要的。同时你可以打开 chrome 的开发者工具运行一下这段 js 脚本

完整代码

这段代码一共也就只有70多行,比较简陋,情根据自己的实际需求更改。

import os
import time
import json
from urllib.parse import urlsplit
import asyncio
import pyppeteer
from scripts import scripts

BASE_DIR = os.path.dirname(__file__)

async def intercept_request(req):
    """请求过滤"""
    if req.resourceType in ["image", "media", "eventsource", "websocket"]:
        await req.abort()
    else:
        await req.continue_()

async def intercept_response(res):
    resourceType = res.request.resourceType
    if resourceType in ["xhr", "fetch"]:
        resp = await res.text()

        url = res.url
        tokens = urlsplit(url)

        folder = BASE_DIR + "/" + "data/" + tokens.netloc + tokens.path + "/"
        if not os.path.exists(folder):
            os.makedirs(folder, exist_ok=True)
        filename = os.path.join(folder, str(int(time.time())) + ".json")
        with open(filename, "w", encoding="utf-8") as f:
            f.write(resp)

async def main():
    browser = await pyppeteer.launch({
        # "headless": False,
        # "devtools": True
        "executablePath": "/Users/changjiang/apps/Chromium.app/Contents/MacOS/Chromium",
        "args": [
            "--disable-extensions",
            "--hide-scrollbars",
            "--disable-bundled-ppapi-flash",
            "--mute-audio",
            "--no-sandbox",
            "--disable-setuid-sandbox",
            "--disable-gpu",
        ],
        "dumpio": True,
    })
    page = await browser.newPage()

    await page.setRequestInterception(True)
    page.on("request", intercept_request)
    page.on("response", intercept_response)

    await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                            "(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299")
    await page.setViewport({"width": 1080, "height": 960})
    await page.goto("http://yangkeduo.com")
    await page.evaluate("""
            () =>{
                   Object.defineProperties(navigator,{
                     webdriver:{
                       get: () => false
                     }
                   })
            }
        """)
    await page.evaluate("你的那一段页面自动下拉 js 脚本")
    await browser.close()

if __name__ == "__main__":
    asyncio.run(main())

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/43958.html

相关文章

  • Google推出的爬虫神器Pyppeteer,神挡杀神,佛挡杀佛!

    摘要:注意,是叫做,不是。两款浏览器同根同源,它们有着同样的,但配色不同,由蓝红绿黄四种颜色组成,而由不同深度的蓝色构成。另外是基于的新特性实现的,所以它的一些执行也支持异步操作,效率相对于来说也提高了。是否响应信号,一般是命令,默认是。 如果大家对 Python 爬虫有所了解的话,想必你应该听说过 Selenium 这个库,这实际上是一个自动化测试工具,现在已经被广泛用于网络爬虫中来应对 ...

    Fundebug 评论0 收藏0
  • 首次公开,整理12年积累的博客收藏夹,零距离展示《收藏夹吃灰》系列博客

    摘要:时间永远都过得那么快,一晃从年注册,到现在已经过去了年那些被我藏在收藏夹吃灰的文章,已经太多了,是时候把他们整理一下了。那是因为收藏夹太乱,橡皮擦给设置私密了,不收拾不好看呀。 ...

    Harriet666 评论0 收藏0
  • Python学到什么程度才可以去找工作?掌握这4点足够了!

    摘要:接下来我们就来看看学到什么程度才算是真正学会可以去一展身手。一确立目标了解需求做什么事情都要先确定好目标,才不至于迷失方向。 大家在学习Python的时候,有人会问Python要学到什么程度才能出去找工作,对于在Python培训机构学习Python的同学来说这都不是问题,因为按照Python课程大纲来,一般都不会有什么问题,而对于自学Python来说,那就比较难掌握,冒然出去找工作非常...

    Yuqi 评论0 收藏0
  • Python爬虫偷懒神器 —— 一键构造请求头

    摘要:但是手动复制粘贴字典里的每一个键值对太费事了一个不那么方便的解决方案用正则表达式或者直接字符串替换,把字符串直接转化为字典,封装成函数方便以后反复调用。有的人喜欢用这种方法,每次复制信息,然后调用自己封装好的函数,但我觉得还是挺麻烦的。 今天介绍个神奇的网站!堪称爬虫偷懒的神器! 我们在写爬虫,构建网络请求的时候,不可避免地要添加请求头( headers ),以 mdn 学习区为例,我...

    charles_paul 评论0 收藏0
  • Python爬虫:通过做项目,小编了解了酷狗音乐的加密过程

    摘要:获取音乐播放列表其实,这就是小编要讲的重点,因为就是这部分用到了加密。 1.前言 小编在这里讲一下,下面的内容仅供学习参考,切莫用于商业活动,一经被相关人员发现,本...

    张宪坤 评论0 收藏0

发表评论

0条评论

Karuru

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<