Last active
December 6, 2024 14:30
-
-
Save yige233/274fbd29ba26a6f9d75d698c1790e0d5 to your computer and use it in GitHub Desktop.
Kindle 中国 批量下载自己的电子书和个人文档
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 批量下载自己已购买的电子书和个人文档 | |
* 要求:至少有一台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("任务结束……"); | |
} |
同样网页直接跳转到 amazon.cn/error
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
试试浏览器的无痕模式呢?