微信读书热门划线
JavaScript 脚本0.前言
一个用于查询微信读书书籍热门划线的工具,支持按章节分组。
1.使用方法
- 打开微信读书网页版:https://weread.qq.com/
- 登录你的账号
- 找到你想要查询的书籍
- 打开浏览器开发者工具(F12)
- 切换到Network标签页,找到类似
info?bookId=3300024284的请求,3300024284即为书籍ID - 切换到Console标签页
- 修改
weread-hotlines.js中的bookId为目标书籍ID:
const bookId = '3300024284'; // 替换为你的书籍 ID
- 将
weread-hotlines.js的内容复制粘贴到控制台执行 - 等待脚本执行完成,复制输出的数据
2.配置说明
weread-hotlines.js 配置项
| 参数 | 说明 | 默认值 |
|---|---|---|
bookId | 书籍 ID | 3300024284 |
batchSize | 批量处理大小 | 50 |
| 请求延迟(章节) | 获取章节划线的延迟 | 2000ms |
| 请求延迟(评论) | 获取划线详情的延迟 | 3000ms |
3.注意事项
⚠️ 封禁风险警告
使用本工具存在账号被封禁的风险!请务必注意:
- ❌ 不要频繁运行此脚本
- ❌ 不要设置过小的请求延迟时间
- ❌ 不要短时间内查询大量书籍
- ❌ 不要在多个设备同时使用
- ✅ 建议使用非主账号测试
- ✅ 如遇异常,立即停止使用
作者不对因使用本工具导致的任何账号问题负责,使用前请充分评估风险!
- 请求频率:脚本内置了请求延迟,请勿设置过小,避免触发反爬机制
- Cookie 有效期:需要在微信读书网页版登录状态下运行脚本
- 数据隐私:请勿分享包含个人信息的数据
- 合理使用:仅用于个人学习和研究,请勿用于商业用途
- 版权声明:获取的内容版权归原作者所有
免责声明:本工具仅供学习和研究使用,请勿用于商业用途。使用本工具可能存在账号封禁风险,使用前请充分评估。使用本工具产生的任何法律责任和账号问题由使用者自行承担,作者不承担任何责任。
附:weread-hotlines.js代码
/**
* 微信读书热门划线爬取脚本
*
* ⚠️ 警告:使用本脚本存在账号被封禁的风险!
*
* 风险提示:
* - 不要频繁运行此脚本(建议间隔 1-2 小时以上)
* - 不要修改请求延迟时间(可能触发反爬机制)
* - 不要短时间内爬取大量书籍
* - 建议使用非主力账号测试
* - 如遇验证码或异常,立即停止使用
*
* 免责声明:
* 使用本脚本产生的任何后果由使用者自行承担
* 作者不对账号封禁等问题负责
*
* 使用方法:
* 1. 在微信读书网页版登录
* 2. 修改下方的 bookId 为目标书籍 ID
* 3. 在浏览器控制台(F12 Console)中运行此脚本
*/
const bookId = '3300024284';
async function getChapterIds() {
try {
const response = await fetch(
'https://weread.qq.com/web/book/chapterInfos',
{
method: 'POST',
credentials: 'include', // 包含 cookie
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({bookIds: [bookId]}),
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// console.log('获取章节信息成功:', JSON.stringify(data, null, 2));
// 从返回值的 data[0] 的 updated 的每一项的 chapterUid 取章节 ID
if (data.data?.[0]?.updated) {
const chapterIds = data.data[0].updated.map((item) => item.chapterUid);
console.log(`\n总共获取 ${chapterIds.length} 个章节 ID:`, chapterIds);
return chapterIds;
} else {
console.warn('返回数据结构不符合预期');
return [];
}
} catch (error) {
console.error('获取章节信息失败:', error);
return [];
}
}
async function getUnderlines(chapterIds) {
if (!chapterIds || chapterIds.length === 0) {
console.log('没有章节 ID,无法获取划线数据');
return [];
}
const allUnderlines = [];
// 遍历每个章节 ID,获取该章节的划线数据
for (let i = 0; i < chapterIds.length; i++) {
const uid = chapterIds[i];
console.log(
`\n获取第 ${i + 1}/${chapterIds.length} 个章节的划线数据 (chapterUid: ${uid})`
);
try {
const response = await fetch(
`https://weread.qq.com/web/book/underlines?bookId=${bookId}&chapterUid=${uid}`,
{
method: 'GET',
credentials: 'include', // 包含 cookie
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const underlines = data.underlines || [];
console.log(` 获取成功,共 ${underlines.length} 条划线`);
// 为每条划线添加对应的 chapterUid
underlines.forEach((underline) => {
allUnderlines.push({
...underline,
chapterUid: uid,
});
});
// 添加延迟,避免请求过快
if (i < chapterIds.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
} catch (error) {
console.error(` 获取第 ${i + 1} 个章节的划线数据失败:`, error);
}
}
console.log(`\n总共获取 ${allUnderlines.length} 条划线`);
return allUnderlines;
}
async function callReadReviewsInBatches(underlines, batchSize = 50) {
if (!underlines || underlines.length === 0) {
console.log('没有划线数据');
return [];
}
console.log(`总共获取 ${underlines.length} 条划线`);
const allAbstracts = [];
// 分批处理
for (let i = 0; i < underlines.length; i += batchSize) {
const batchUnderlines = underlines.slice(i, i + batchSize);
// 按 chapterUid 分组,因为同一个请求中的所有 range 必须来自同一个章节
const groupedByChapter = {};
batchUnderlines.forEach((underline) => {
const uid = underline.chapterUid;
if (!groupedByChapter[uid]) {
groupedByChapter[uid] = [];
}
groupedByChapter[uid].push(underline);
});
// 为每个章节发送一个请求
for (const [chapterUid, chapterUnderlines] of Object.entries(
groupedByChapter
)) {
const reviews = chapterUnderlines.map((underline) => ({
range: underline.range,
maxIdx: 0,
count: 1,
synckey: 0,
}));
const payload = {
bookId,
chapterUid: parseInt(chapterUid),
reviews,
};
console.log(
`\n处理第 ${Math.floor(i / batchSize) + 1} 批,chapterUid: ${chapterUid},共 ${reviews.length} 条划线`
);
console.log('请求参数:', JSON.stringify(payload, null, 2));
try {
const response = await fetch(
'https://weread.qq.com/web/book/readReviews',
{
method: 'POST',
credentials: 'include', // 包含 cookie
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// 从返回结果中提取 abstract 字段
if (result.reviews && Array.isArray(result.reviews)) {
result.reviews.forEach((review) => {
if (
review.pageReviews &&
Array.isArray(review.pageReviews) &&
review.pageReviews.length > 0
) {
const firstPageReview = review.pageReviews[0];
if (firstPageReview.review?.abstract) {
allAbstracts.push([firstPageReview.review.chapterTitle, firstPageReview.review.abstract]);
}
}
});
}
} catch (error) {
console.error(`处理 chapterUid: ${chapterUid} 的请求失败:`, error);
}
// 添加延迟,避免请求过快
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
console.log(`\n总共提取 ${allAbstracts.length} 条 abstract`);
console.log('所有 abstract 内容:', JSON.stringify(allAbstracts, null, 2));
return allAbstracts;
}
/**
* 主函数
*/
async function main() {
console.log('开始获取微信读书数据...\n');
// 先获取所有章节 ID
console.log('=== 第一步:获取书籍的全部章节 ID ===');
const chapterIds = await getChapterIds();
console.log('\n=== 第二步:获取划线数据 ===');
const underlines = await getUnderlines(chapterIds);
console.log('\n=== 第三步:批量获取评论 ===');
const abstracts = await callReadReviewsInBatches(underlines, 50);
console.log('\n所有请求完成!');
console.log(`\n最终结果:共获取 ${abstracts.length} 条 abstract`);
console.log(abstracts);
return abstracts;
}
// 执行主函数
main().catch(console.error);