Skip to content

Instantly share code, notes, and snippets.

@yige233
Last active December 6, 2024 14:30
Show Gist options
  • Save yige233/274fbd29ba26a6f9d75d698c1790e0d5 to your computer and use it in GitHub Desktop.
Save yige233/274fbd29ba26a6f9d75d698c1790e0d5 to your computer and use it in GitHub Desktop.
Kindle 中国 批量下载自己的电子书和个人文档
/**
* 批量下载自己已购买的电子书和个人文档
* 要求:至少有一台Kindle设备。
* 打开 https://www.amazon.cn/hz/mycd/myx/ ,然后按F12键进入Console(控制台),把代码全部复制并粘贴到控制台中,回车。
* 然后输入 download("ebook") ,下载所有的电子书
* 想下载个人文档,则是输入 download("pdoc")
* 下载时如果某个文件下载失败,可以使用刚刚运行的函数(也就是 download() 或者 download("pdoc") )重新开始下载。在网页没被关闭的情况下,程序会忽略已经下载了的文件。
* 脚本运行期间请不要关闭网页,请允许网页自动下载多个文件
* 如果网页被关闭了,但恰巧你保存了上次下载任务返回的成功下载的文件列表,
* 可以选择复制该列表中的所有文字,并将其作为 download 的第二个参数传入(如 download("ebook",["something","something else"]) ),这样程序同样会忽略已经下载了的文件。
*
* 原来通过请求获取下载url的方法只适用于电子书,虽然有意识到下载链接似乎有一定规律,但也没多想,
* 后来看到 https://github.com/yihong0618/Kindle_download_helper 这个项目,发现他是用拼接url而非请求获取url,
* 这样可以获取个人文档的下载链接,于是研究了下拼接用的参数,现在这个脚本也能下载个人文档了
* 在 @Xpink1999 的帮助下,程序现在可以正常处理超过1000本书了。(成功下载了1763本书)
*
* 2024/6/9: 修改了下载模块,避免产生“fetch时设置了‘credentials: "include"’的情况下,响应cros头是‘*’,则请求失败”的情况。
* 这样的下载方式实际上和网页的下载方式相同,但也因此,程序无法检测书本是否下载成功。
*
* @param {String} type 默认为 "ebook" ,下载电子书,若为 "pdoc", 则是下载个人文档
* @param {Array} completedList 数组,如果一本电子书的asin在其中就不会下载
* @param {Number} timeout 每个下载操作间隔的时间,单位为秒,默认为 20s
*
*/
async function download(type = "ebook", completedList = [], timeout = 20) {
function HTMLdecode(str) {
const textarea = document.createElement("textarea");
textarea.innerHTML = str;
return textarea.value;
}
async function request(activity, input) {
const result = await fetch("https://www.amazon.cn/hz/mycd/digital-console/ajax", {
headers: {
"content-type": "application/x-www-form-urlencoded",
},
method: "POST",
body: [["activity", activity].join("="), ["activityInput", JSON.stringify(input)].join("="), ["csrfToken", encodeURIComponent(window.csrfToken)].join("=")].join("&"),
credentials: "include",
});
const resType = result.headers.get("content-type");
if (result.status == 200 && resType.includes("application/json")) {
return await result.json();
}
console.warn({ 状态: "请求失败", 状态码: result.status, post: { activity, input }, response: await result.text() });
return {};
}
async function dlFile(url, fileName = "未命名文件") {
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
await new Promise((resolve) => setTimeout(resolve, timeout * 1000));
console.log("下载完成", fileName);
}
const batchSize = 1000,
newCompletedList = [...completedList],
docType = type == "pdoc" ? "PDOC" : "EBOK",
ownershipDataCommon = {
showSharedContent: true,
fetchCriteria: { sortOrder: "DESCENDING", sortIndex: "DATE", startIndex: 0, batchSize: batchSize, totalContentCount: -1 },
surfaceType: "LargeDesktop",
},
ownershipDataEbook = {
contentType: "Ebook",
contentCategoryReference: "booksAll",
itemStatusList: ["Active"],
excludeExpiredItemsFor: ["KOLL", "Purchase", "Pottermore", "FreeTrial", "DeviceRegistration", "KindleUnlimited", "Sample", "Prime", "ComicsUnlimited", "Comixology"],
originTypes: [
"Purchase",
"PublicLibraryLending",
"PersonalLending",
"Sample",
"ComicsUnlimited",
"KOLL",
"RFFLending",
"Pottermore",
"Prime",
"Rental",
"DeviceRegistration",
"FreeTrial",
"KindleUnlimited",
"Comixology",
],
},
ownershipDataPDoc = {
contentType: "KindlePDoc",
contentCategoryReference: "pdocs",
itemStatusList: ["Active"],
},
{
success = false,
GetDevicesOverview: {
deviceList: [{ deviceSerialNumber = null, deviceTypeID = null, customerID = null }],
},
} = await request("GetDevicesOverview", { surfaceType: "LargeDesktop" });
let bookCount = 0;
if (!deviceSerialNumber || !success) {
return console.warn("获取Kindle设备信息失败");
}
while (true) {
const data = type == "pdoc" ? ownershipDataPDoc : ownershipDataEbook;
const {
success = false,
GetContentOwnershipData: { items = [], numberOfItems = 0 },
} = await request("GetContentOwnershipData", Object.assign({}, ownershipDataCommon, data));
if (!success) {
return console.warn("获取书本信息失败");
}
for (const { asin, authors, author, title } of items) {
if (newCompletedList.includes(asin)) continue;
const url = `https://cde-ta-g7g.amazon.com/FionaCDEServiceEngine/FSDownloadContent?type=${docType}&key=${asin}&fsn=${deviceSerialNumber}&device_type=${deviceTypeID}&customerId=${customerID}&authPool=AmazonCN`,
fileName = `${HTMLdecode(authors || author)} - ${HTMLdecode(title)}.azw3`;
try {
console.log("开始下载书籍:", fileName, "ASIN:", asin);
await dlFile(url, fileName);
newCompletedList.push(asin);
} catch (err) {
console.warn(err);
}
}
ownershipDataCommon.fetchCriteria.startIndex += batchSize;
bookCount += items.length;
if (bookCount >= numberOfItems) break;
}
console.log("Kindle 设备序列号:", deviceSerialNumber, "可以用于为下载的电子书移除DRM。个人文档无需去除DRM");
console.log("下方的内容,是本次下载任务中,已经完成下载的电子书的数据。将其作为 download 函数的第二个参数传入,则该次下载任务会忽略这些已下载的电子书。");
console.log(newCompletedList);
console.log("任务结束……");
}
@Xpink1999
Copy link

页面是一样的,今天又出现了新的错误,因为不是专业计算机,可能不是很明白,也是通过网络搜到这个解决方法。下载自购书籍没有问题,但是个人文档一直不行

很奇怪,看起来由于某种原因,第二个ajax请求被重定向到了https://www.amazon.cn/500 ,你可以看看你截图里第二个ajax的状态码是不是3开头的
试试看这个gist:https://gist.github.com/smartchaos/fcf95f4ebe21059d5eb5241bef87f5c1 ,如果可以用的话还请告知我一声

新代码有了更多的ajax,第一个的状态码是302,其余均是200 又试了一遍就代码,也同样很多ajax,第一个的状态码也是302,其余200

我又去看了一遍官方网页是如何获取设备列表的,发现它用了新的API,但很神奇的是我代码中旧的API也能工作,我现在怀疑旧API在你那不能用 你可以用下面的代码验证一下我的想法,复制到控制台执行就行

提示新旧API都可以工作

我更新了下这个gist,再试试看呢?如果还是行,新的代码应该会给出更多有用的信息

可以工作了。但是中间会出现一些错误代码,不知道是什么意思

可以正常工作了就好。 你的截图中的这些错误都不是由这个gist的代码引发的。可以看到每条报错的最右侧,那里说明了这个报错是哪里来的。如果是在控制台中执行的代码,是以“VM”开头的。

好的。多谢大佬

@Xpink1999
Copy link

还要咨询一下大佬,如果超过1000本书怎么继续,代码我需要修改哪些地方?Kindle_download_helper这个项目我有尝试,但是获取列表很不稳定,有时候只能抓取到几百本甚至几十本书,这个项目非常稳定,很好用。

@yige233
Copy link
Author

yige233 commented Apr 8, 2023

还要咨询一下大佬,如果超过1000本书怎么继续,代码我需要修改哪些地方?Kindle_download_helper这个项目我有尝试,但是获取列表很不稳定,有时候只能抓取到几百本甚至几十本书,这个项目非常稳定,很好用。

试试我新修改的这份代码
原来的代码是有处理获取1000本之后的书的情况的,但似乎完全没用😂因为我本身没有超过1000本书,所以没条件去测试,新的处理逻辑也不能保证一定生效

@Xpink1999
Copy link

还要咨询一下大佬,如果超过1000本书怎么继续,代码我需要修改哪些地方?Kindle_download_helper这个项目我有尝试,但是获取列表很不稳定,有时候只能抓取到几百本甚至几十本书,这个项目非常稳定,很好用。

试试我新修改的这份代码 原来的代码是有处理获取1000本之后的书的情况的,但似乎完全没用😂因为我本身没有超过1000本书,所以没条件去测试,新的处理逻辑也不能保证一定生效

感谢,非常完美,第一次就差四本下载失败,继续后也都成功了,一共1763项均成功,再次感谢

@shakespark
Copy link

感谢,下载成功

@CheesePower7
Copy link

CheesePower7 commented Jun 17, 2024

请问,为什么打开网页 https://www.amazon.cn/hz/mycd/myx/ 我运行这个脚本之后,网页直接跳转到 amazon.cn/error

@yige233
Copy link
Author

yige233 commented Jun 17, 2024

请问,为什么打开网页 https://www.amazon.cn/hz/mycd/myx/ 我运行这个脚本之后,网页直接跳转到 amazon.cn/error

试试浏览器的无痕模式呢?

@TheoHLong
Copy link

同样网页直接跳转到 amazon.cn/error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment