这些年我一直跟着维护Pt-Gen以及PTPP、豆瓣搜索大师等公开项目。然而19年起,豆瓣逐渐关闭公开API (豆瓣疑下线所有公开 API),从现有豆瓣APP的 frodo 接口请求相关数据,所需要构造的参数过于麻烦。
所以在这段时间,我们一直使用 https://movie.douban.com/j/subject_suggest
接口来实现搜索功能作为过渡。这个接口构造简单,而且返回的数据直接为JSON格式,但同样存在返回的数据较为简单的问题,例如:
[
{
"episode": "",
"img": "https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp",
"title": "肖申克的救赎",
"url": "https://movie.douban.com/subject/1292052/?suggest=%E8%82%96%E7%94%B3%E5%85%8B",
"type": "movie",
"year": "1994",
"sub_title": "The Shawshank Redemption",
"id": "1292052"
},
{
"episode": "",
"img": "https://img1.doubanio.com/f/movie/b6dc761f5e4cf04032faa969826986efbecd54bb/pics/movie/movie_default_small.png",
"title": "肖申克的救赎:主演访谈",
"url": "https://movie.douban.com/subject/35278770/?suggest=%E8%82%96%E7%94%B3%E5%85%8B",
"type": "movie",
"year": "2004",
"sub_title": "The Shawshank Redemption: Cast Interviews",
"id": "35278770"
}
]
只返回了img, title, url, type, id
(虽然基本够用....)。这主要是因为该接口只是用来搜索建议的,而真正搜索界面的结果隐藏在了subject_search页面的 __DATA__
对象中。所以我们需要用另外一种方法来破解该对象。
我最早找到的文章来自掘金的这篇 豆瓣读书搜索页的window.__DATA__的解密
,然而作者的思路是直接扒取豆瓣自带的JS文件(这当然没有问题)。然而作者讲的挺玄乎的: 总之很复杂,这个只能意会不能言传,篇幅有限,也不可能全部一个一个扣出来并和你说怎么改。
正当我以为没有办法,只能复制这位作者成果的时候(毕竟顶层函数有了,而且我也是在 JavaScript 环境中使用,不需要在Python中调用),我检索到了另外一个项目 dli98/Spider,文中作者详细介绍了 window.__data__
参数破解过程是使用 base64 + xxHash + rc4 + bplist 一整套的思路,并指出 bplist 处理流程有暗改的情况。
但这位作者的结果其实并不对,少了最后一步的映射调整 (见 怎么获取豆瓣评分和投票数的顺序 · Issue #2 · dli98/Spider),所以得到的结果并不对。
所以就干脆直接写个 npm 库算了,之后工程中也能使用。于是你现在可以使用 npm i douban-search-crack
来进行安装,其实例代码如下:
import axios from 'axios';
import decryptDoubanData, { extractDataFromPage } from 'douban-search-crack';
const { data: doubanSearchPage } = await axios.get('https://search.douban.com/movie/subject_search', {
params: {
search_text: '肖申克的救赎',
cat: 1002
},
responseType: 'text'
})
const encryptDoubanData = extractDataFromPage(doubanSearchPage)
console.log(decryptDoubanData(encryptDoubanData))
函数主流程如下:
export default function decryptDoubanData(dataRawString: string): searchData {
// 将网页字符串解析为base64
const dataRaw = Buffer.from(dataRawString, 'base64')
// 从base64中提取secKey,并整理生成需要解密的数据
const i = 16
const s = Math.max(Math.floor((dataRaw.length - 2 * 16) / 3), 0)
const u = dataRaw.slice(s, s + i)
const secKey = XXH.h64(u, 41405).toString(16)
const encryptData = Buffer.concat([dataRaw.slice(0, s), dataRaw.slice(s + i)])
// rc4解密
const cipher = new RC4(secKey);
const decryptData = cipher.update(encryptData);
// BpList解密
const bpList = parseBPlist(decryptData)
// 对BpList的结果进行修正并返回
return fixture.parse(bpList)
}
其中 xxhash 部分直接用库;rc4 因为过于简单,所以方法也直接内置;bplist解密因为豆瓣的暗改,所以不能直接用库,所以也是将库文件修改后内置的(原来想学着用monkeypatch的,然而patch发现还不如直接内置)。
此外,bpList部分,对比库原实现,除调换了objType= 4,5,6的解析方式,还删除了 对库 bigInt
的依赖,以及对header的检查。
而 fixture 模块,主要因为 BpList 返回的值主要为一个list,里面的Object字段基本如下:
{k: [ 40, 27, 35, 50, 52, 44 ] }
{k: [ '', [] ] , z: [ 44, 55 ] }
{k: [ '', [] , {j: 55} ], z: [ 44, 66, 88 ]}
所以我们需要进行修正,其中各数字均表示其在原list中的位置。通过对比 sergiojune 扒出来的代码,形成了矫正的步骤,具体可见 utils/fixture.ts
文件。
那么,自此,你就可以使用 douban-search-crack
来在node以及browser环境中来破解豆瓣搜索的DATA数据。与直接扒网页js相比,如果你在node环境中,那么总文件大小 仅 16.0 KB (16,452 字节)。而如果你要在Browser环境中使用,那么也可以直接使用打包好的 dist/bundle.js
文件,其大小为 47.0 KB (48,147 字节),与直接扒拉js的实现大小基本相同。
大神微信群拉我个 我微信 aboutsiyu88
不好意思,我从来不在微信上加陌生人
大神,请问怎么自己添加一些资源网站到豆瓣搜索大师呢
不属于本文讨论范围,请自己查看“豆瓣搜索大师”脚本注释