【作者主页】:吴秋霖
【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作!
【作者推荐】:对JS逆向感兴趣的朋友可以关注《爬虫JS逆向实战》,对分布式爬虫平台感兴趣的朋友可以关注《分布式爬虫平台搭建与开发实战》
还有未来会持续更新的验证码突防、APP逆向、Python领域等一系列文章
??除了对x-s、x-s-common进行分析实现加密算法,还有之前文章中提到的通过JS注入免扣加密算法的方式获取加密参数
加密分析及算法文章请阅读这篇文章:小红书x-s、x-s-common加密分析(2024-01-10更新)
x-s的加密算法为JS实现、x-s-common的加密算法为Python实现
可以看到上图断点处l包含x-s跟x-t的返回,看下面这行代码:
l = (a && void 0 !== window._webmsxyw ? window._webmsxyw : encrypt_sign)(s, i) || {};
window._webmsxyw函数内即加密逻辑,在自执行函数内部并添加在了window属性中
该函数接受两个参数,s是api接口的路径,i是请求提交的参数
可以使用Playwright或者pyppeteer实现,通过浏览器的JavaScript注入来获取加密参数,代码实现分别如下
Playwright方式:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=True)
page = await browser.new_page()
# 注入stealth.min.js脚本
await page.add_init_script(path="stealth.min.js")
url = "" # 请求api
data = "" # 请求参数
# 执行JavaScript
encrypt_params = await page.evaluate('([url, data]) => window._webmsxyw(url, data)', [url, data])
local_storage = await page.evaluate('() => window.localStorage')
print(encrypt_params)
print(local_storage)
await browser.close()
asyncio.run(main())
pyppeteer方式:
import asyncio
from pyppeteer import launch
async def main():
browser = await launch(headless=True)
page = await browser.newPage()
# 注入stealth.min.js脚本
stealth_script = open("stealth.min.js", "r").read()
await page.evaluateOnNewDocument(stealth_script)
url = "" # 请求api
data = "" # 请求参数
# 执行JavaScript
encrypt_params = await page.evaluate('([url, data]) => window._webmsxyw(url, data)', [url, data])
local_storage = await page.evaluate('() => window.localStorage')
print(encrypt_params)
print(local_storage)
await browser.close()
asyncio.get_event_loop().run_until_complete(main())
上面的stealth.min.js脚本注入的作用是为了防止被检测的,另外cookie参数需要设置属性来避免Web端出现滑动验证码
当然,这个都是爬虫最终工程化需要考虑的事情,这里主要还是通过非逆向分析的方式去解决加密参数问题!
window.localStorage在之前加密分析的文章中已经详细介绍了,localStorage是一个在浏览器中存储键值对的API,通常用于持久化地存储数据,所需的b1参数就在其中
JS注入方式运行结果如下所示:
x-s跟x-t的加密参数通过注入的方式能够直接拿到,但是x-s-common的参数仍需要通过sign的方法加密计算生成!
Python版本的sign加密算法在之前的加密分析文章中已提供!JS注入的方式主要为了获取这些个参数:x-s、x-t、b1
JS注入的方式对于有前端基础及经验的小伙伴,就很简单了。通过上面的方式获取到所有的加密参数后,接下来就是爬虫的工程化
以笔记搜索为例,爬虫代码实现如下:
import json
import httpx
from typing import Dict, Optional
async def request(self, method, url, **kwargs) -> Dict:
async with httpx.AsyncClient(proxies=self.proxies) as client:
response = await client.request(
method, url, timeout=self.timeout,
**kwargs
)
data: Dict = response.json()
if data["success"]:
return data.get("data", data.get("success", {}))
elif data["code"] == self.IP_ERROR_CODE:
raise IPBlockError(self.IP_ERROR_STR)
else:
raise DataFetchError(data.get("msg", None))
async def unified_request(self,
uri: Optional[str] = None,
data: Optional[dict] = None,
keyword: Optional[str] = None,
page: Optional[int] = 1,
page_size: Optional[int] = 20,
sort: Optional[SearchSortType] = SearchSortType.GENERAL,
note_type: Optional[SearchNoteType] = SearchNoteType.ALL) -> Dict:
if keyword:
_host = "https://edith.xiaohongshu.com"
uri = "/api/sns/web/v1/search/notes"
data = {
"keyword": keyword,
"page": page,
"page_size": page_size,
"search_id": get_search_id(),
"sort": sort.value,
"note_type": note_type.value
}
elif uri and data:
headers = await self._pre_headers(uri, data)
json_str = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
return await self.request(method="POST", url=f"{self._host}{uri}",
data=json_str, headers=headers)
else:
raise ValueError("Either 'uri' and 'data' or 'keyword' must be provided.")
return await request(method="POST", url=f"{_host}{uri}", data=json.dumps(data), headers=await self._pre_headers(uri, data))
最后,订阅的小伙伴可找作者获取开箱即用的完整爬虫项目代码,如下:
JS注入方式笔记搜索:
JS注入方式笔记评论: