吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4186|回复: 20
收起左侧

[其他原创] 分析书链 Ebook 书籍的图片下载链接并生成带书签的 PDF

  [复制链接]
LoveCode 发表于 2023-10-20 23:07

缘起

之前看到了这篇帖子 https://www.52pojie.cn/forum.php?mod=viewthread&tid=1842462&highlight=%CD%BC%C6%AC

现在有时间整理好思路、并写成文章发布。

前言

经过我在 必应搜索、吾爱破解上的搜索与调查,有软件可以下载书链 bookln 上的图书,也有一些文章从 js 逆向写 python 代码批量下载。

诚然,在浏览器中批量下载图片最简单的方式是使用 “图片批量下载插件”、或者油猴插件中现有的 JS 代码,然吾辈修仙者(代码修仙)虽有前人经验传承、功法灵器,但若不加以试炼、斗法,又如何成为一方顶天立地的强者?

虽有人反驳:“能杀敌就行(能达到目的,下载到内容就行)”,但不要忘了,仙道渺渺,大道无情,今有前人炼制的灵器(写好的程序)可以制敌,但未来命运无常,遇凶险绝地而灵器被压制又奈何?? 是以:最重要的还是个人修为,除非不修行了

所以本文最主要的是 分析 JS 代码的思路、编写 Python 代码生成带书签的 PDF 的思路。网页的分析是在北京时间 10/20/2023 进行,将来网页变动此文也不会去适应网站的变化,不过此文阅读下来想必大家对于今后的斗法有一定的帮助。

另外,本人现在练气一层中期,只好选择这种一阶凶兽作为练法对手了。

战斗准备

斗法战场:window 10, 浏览器 Edge

携带凡器:postman

法力派别:python 3.10

关于书链中书籍的链接分类

经过调查,书链的链接有以下几种分类:

  • sample.htm 结尾,其处理方式应该和 sample2 一样。
  • sample2.htm 结尾,如 https://mp.zhizhuma.com/book/sample2.htm?code=174ef803EE&shelfCode=D5831a7B3

在网页 [【爬虫项目】书链平台试卷下载 - 大数据男孩 - 学习编程路上的点点滴滴 (bigdataboy.cn)](https://bigdataboy.cn/post-205.html) 处已有人写了分析过程。

  • ebook.html 结尾,如 https://mp.bookln.cn/book/ebook.htm?bookId=385519&srcchannel=mp

此种形式我没有在网上搜到相关的文章,所以正如本文的标题所言,本次分析就是 ebook 结尾的书籍。


网页寻踪!确定数据来源

在正式斗法之前需要明确目标:

  1. 将书籍的图片都下载下来
  2. 将图片生成 PDF
  3. 有书签的话就添加书签咯

好!现在开始,打开网页 https://mp.bookln.cn/book/ebook.htm?bookId=385519&srcchannel=mp

image-20231020134923280.png

第一步,是要确定它到底收不收费,收费的话咱还是溜了吧…………还好经过测试,它不收费,那继续。

image-20231020135504258.png

然后在抓包界面先查看图片的请求,知道了它是 .jpg 格式。

image-20231020135948613.png

然后在 HTML 中搜索 .jpg、或者看一看有什么可疑的数据,毕竟通常都存在 此地无银三百两 的情况,不过经过实验都没有找到结果,那只好从请求中分析了。

image-20231020140242364.png

确定关键的请求 detail_do_url

首先大致浏览所有请求,发现了一个最可疑的链接,因为它包含了书籍的详细信息——除了图片的链接。

image-20231020140541317.png

现在为了便于讨论,我将上述请求的链接暂时命名为 detail_do_url,将它返回的 JSON 数据称之为 detail_do_data.

该请求的具体细节暂时不用细究,因为还没有找到书籍的图片下载链接的逻辑。

确定图片下载链接的来源 mediaplay_do_url

现在,知道了 detail_do_data 也不清楚图片链接是怎么来的呀,那就只好从图片请求入手了,发现了重点!图片链接是从另一个请求来的!

image-20231020141855038.png

另外,上面那个绿色的行是 Edge 的一个功能:按住键盘的 Shift 键,然后鼠标移动到某个请求中,它能找到该请求来自哪一个请求的内容。我也是最近看 B 站的逆向视频 学到的。

image-20231020142203729.png

所以现在来到了那个 mediaplay_do_url注意,这里又暂时起了一个名字哟,查看该请求的信息。

image-20231020142603425.png

没错!细心的大家想必发现了重点!

image-20231020143048175.png

确定流程

现在就可以基本确定整个处理过程了,如下图所示(什么??不记得下面各个名词的含义了??)

image-20231020221957214.png


法力激荡!力破 detail_do_url

根据上文的分析,现在需要获取到 detail_do_data,自然就要分析 detail_do_url 了,下面是它的请求信息,能看得出的先做出判断:

# POST 请求
https://biz.bookln.cn/ebookservices/detail.do

# 请求头如下
headers = {
    "authority": "biz.bookln.cn",
    "accept": "application/json, text/javascript, */*; q=0.01",
    "accept-language": "en",
    "cache-control": "no-cache",
    "content-type": "application/x-www-form-urlencoded; charset=UTF-8",

    # 这个可能需要
    "origin": "https://mp.bookln.cn",
    "pragma": "no-cache",

    # 这个可能需要
    "referer": "https://mp.bookln.cn/",
    "sec-ch-ua": "^\\^Chromium^^;v=^\\^118^^, ^\\^Microsoft",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "^\\^Windows^^",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46"
}

# POST 的值
data = {
    # 书籍的 ID,还记得这本书的链接吗??就在那里
    "bookId": "385519",
    # 时间戳
    "_timestamp": "1697781426",
    "_nonce": "ca32554fe7ef41c588b5aa9dcfca1d0d",
    "_deviceid": "yda2ce4kwupcksvb89214",
    "_traceId": "2023102013570630227139921303821c",
    "_sign": "0DD682FCDAABE7685537"
}

其它的只好细细分析了。这里我就全局搜索了,因为这些 POST 的字段名都很有个性,所以很好找,如下。

image-20231020144542141.png

image-20231020222152377.png

下面开始一一确认 POST detail_do_url 时所需要的数据。

关于 bookId

本书籍的链接是 https://mp.bookln.cn/book/ebook.htm?bookId=385519&srcchannel=mp,所以可以轻松获取。

关于 _timestamp

是一个时间戳,但要注意它只有 10 位数,可用以下 python 代码模拟:

from time import time

_timestamp = str(int(time()))

关于 _nonce

根据代码 e._nonce = requestUuidV4();,自然要进行调试、跟踪。

image-20231020145421415.png

具体代码如下,这里出现了 random,那就说明这个函数的返回值一点也不重要,因为服务端也不会知道 _nonce 具体的值,只能知道它的形式:如它的长度、它的构成等等

function requestUuidV4() {
    return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(e) {
        // 注意这个 random
        var t = 16 * Math.random() | 0
          , n = "x" == e ? t : 3 & t | 8;
        return n.toString(16)
    })
}

所以我们直接随便拿一个已有的 _nonce 值即可,如下 python 代码:

_nonce = "ee259673286e4b6da55d4691042e67fc"

关于 _deviceid

其具体 js 代码如下:

var n = window.YTLogger;

// 这里可以看到 e 的 _deviceid 和 _traceId 都来上面的变量 n
if (n && n.deviceId && n.traceId) {
    var i = n.deviceId()
    , r = n.traceId();
    i && (e._deviceid = i),
        r && (e._traceId = r)
}

跟踪进入 window.YTLogger.devicesId(),最终确定它来自 cookie 中的 _ytdeviceid_ 字段哟。

image-20231020150142739.png

image-20231020150220809.png

image-20231020150305717.png

那么这个 cookie _ytdeviceid_ 是哪里来的??从请求的 cookie 面板中查看其 httpOnly 没有打勾。

恰在此时,想到之前回答某篇帖子时说错了:打了勾说明是服务器设置的,没打勾则只能说明 js 可读写该 cookie 值。

image-20231020225822415.png

先清空网站所有数据,再 hook 它。

image-20231020150835707.png

image-20231020151315607.png

放开上图中的 script 断点,继续执行,看到结果啦!虽然服务器 Set-Cookie 了,但不一定会设置 http only 啦。

image-20231020152122810.png

至此,可以写出如下 python 代码。

import requests

# 需要保存 cookie 就用它啦
SESSION = requests.Session()
url = "https://mp.bookln.cn/book/ebook.htm?bookId=385519&srcchannel=mp"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46"
}

# 因为只需要 Set-Cookie 的值,所以甚至都不需要 get,发送一个 head 就行
SESSION.head(url,headers=headers)
_deviceid = SESSION.cookies["_ytdeviceid_"]
print(_deviceid) # yd71cd5piyue5lz10fa0

关于 _traceId

它和 _deviceid 一样都来自 window.YTLogger,根据代码跟踪进入 window.YTLogger.traceId();

image-20231020152844602.png

image-20231020152913129.png

image-20231020153106680.png

最终确定它来自 sessionStorage

image-20231020153246286.png

image-20231020153310235.png

现在的问题就是这个 tractId 的值是怎么生成的了,如下确认位置在 genTraceId 函数 ,然后下断点。

image-20231020154200055.png

然后需要去清空 sessionStorage

image-20231020153854775.png

刷新网页!进入断点!

image-20231020154338060.png

分析其 JS 代码

代码如下:

// 取自 https://yuntisyscdn.bookln.cn/server/logger/logger_2.3.8.js
t.genTraceId = function() {
    var e = t.current() + t.random() + "";
    return e + C()(e).substring(0, 5)
}
关于 t.current
// 其中 t.current 代码如下
t.current = function() {
    var e = new Date;
    return e.getFullYear().toString() + t.prefixInteger(e.getMonth() + 1, 2) + t.prefixInteger(e.getDate(), 2) + t.prefixInteger(e.getHours(), 2) + t.prefixInteger(e.getMinutes(), 2) + t.prefixInteger(e.getSeconds(), 2) + t.prefixInteger(e.getMilliseconds(), 3)
}

发现它就是 “年、月、日、时、分、秒” 各取 2位数,但是最后的毫秒取 3 位数,可用以下 代码模拟:

from datetime import datetime

e = datetime.now()
# 注意 e.microsecond 取前面 3 位数
current = f"{e.year}{e.month}{e.day}{e.hour}{e.minute}{e.second}{str(e.microsecond)[:3]}"
print(current) # 2023102015514177
关于 t.random
t.random = function() {
    for (var t = "", e = 0; e < 10; e++)
        // 注意这里的 random
        t += parseInt((10 * Math.random()).toString(), 10).toString();
    return t
}

正如之前所说,既然有了 Math.random,所以 t.random() 的值不重要,故直接从控制台输出、拿一个用就行。

image-20231020155412291.png

random = "8243585865"
关于 C()(e)

image-20231020155815451.png

整合代码

现在就要将上述代码整合到一个来生成 _traceId

def get_traceid():
    def get_current():
        """ 相当于 t.current() """

        e = datetime.now()
        # 注意 e.microsecond 取前面 3 位数
        current = f"{e.year}{e.month}{e.day}{e.hour}{e.minute}{e.second}{str(e.microsecond)[:3]}"
        return current

    e = get_current() + "8243585865"
    # 然后进行 MD5 计算,取末尾的 5 位
    return e + hashlib.md5(e.encode()).hexdigest()[:5]

关于 _sign

直接分析 js 代码。

// 这里对 e 的字段名进行排序,然后赋值给 a。
for (var o = "", a = Object.keys(e).sort(), s = 0; s < a.length; s++) {
    // 取 e 的某个字段名
    var l = a\[s\], c = e[l];  // 取该字段对应的值
    "null" != c && null != c && null != c && "undefined" != c || (c = "", e[l] = c),
        o += c + "" + l  // 然后将它们拼接,最终保存到 o 变量
}

// 进行 MD5 啦
e._sign = CryptoJS.MD5(o).toString().toUpperCase().substring(0, 20)

关于上述代码为什么会有 a\[s\] 形式。

image-20231020230637739.png

嗯……简单明了,还原成以下代码。

# 这里是调试时取的 e 的值,是为了最后生成结果时可以和网站的结果相互验证
e = {
    "bookId": "385519",
    "_deviceid": "yd56aa5kwwzogjrdzo8a5",
    "_nonce": "b40f2debf66e4fd7a72542b1891ff4e4",
    "_timestamp": "1697789171",
    "_traceId": "20231020160416548229331105520d30",
}
result = ""
for item in sorted(e.keys()):
    result += e[item] + item

print(result)
e["_sign"] = hashlib.md5(result.encode()).hexdigest().upper()[:20]

获取 detail_do_data

现在需要将上述代码,整合成函数等供将来使用,如下:

import hashlib
from time import time
from datetime import datetime

import requests

SESSION = requests.Session()
NORMAL_HEADERS = {
    "accept": "application/json, text/javascript, */*; q=0.01",
    "accept-language": "en",
    "cache-control": "no-cache",
    "pragma": "no-cache",
    "sec-ch-ua": "^\\^Chromium^^;v=^\\^118^^, ^\\^Microsoft",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "^\\^Windows^^",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46",
}
""" 通用请求头 """

BOOK_API_URL = "https://mp.bookln.cn/book/ebook.htm?bookId={}"
""" 访问 ebook 书籍的链接 """

def visit_book_page(book_id: str):
    """访问书籍的首页,获取 Set-Cookie,即获取 _deviceid"""
    SESSION.head(BOOK_API_URL.format(book_id), headers=NORMAL_HEADERS)

def _get_post_data_for_detail_do_url(book_id) -> dict:
    def get_traceid():
        e = datetime.now()
        # 注意 e.microsecond 取前面 3 位数
        current = f"{e.year}{e.month}{e.day}{e.hour}{e.minute}{e.second}{str(e.microsecond)[:3]}"
        e = current + "8243585865"
        # 然后进行 MD5 计算,取末尾的 5 位
        return e + hashlib.md5(e.encode()).hexdigest()[:5]

    def get_sign(e: dict) -> str:
        result = ""
        for item in sorted(e.keys()):
            result += e[item] + item

        return hashlib.md5(result.encode()).hexdigest().upper()[:20]

    e = {
        "bookId": book_id,
        "_timestamp": str(int(time())),
        "_nonce": "ee259673286e4b6da55d4691042e67fc",
        # 先要确保调用了 visit_book_page 方法哟
        "_deviceid": SESSION.cookies["_ytdeviceid_"],
        "_traceId": get_traceid(),
    }
    e["_sign"] = get_sign(e)
    return e

def get_detail_do_data(book_id: str) -> dict:
    """通过 book_id 访问 detail_do_url 获取 detail_do_data"""
    detail_do_url = "https://biz.bookln.cn/ebookservices/detail.do"
    headers = {
        "authority": "biz.bookln.cn",
        "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
        "origin": "https://mp.bookln.cn",
        "referer": "https://mp.bookln.cn/",
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.46",
    }

    data = _get_post_data_for_detail_do_url(book_id)

    response = SESSION.post(detail_do_url, headers=headers, data=data)

    with open("detail_do_data.json", "w", encoding="utf8") as fp:
        fp.write(response.text)

        return json.loads(response.text)

# 主代码
book_id = "385519"
visit_book_page(book_id)
detail_do_data = get_detail_do_data(book_id)

image-20231020164857322.png


剑势无双!下载图片并生成 PDF

上文保存了获取到的 detail_do_data,现在就从该文件读取内容并批量下载图片。

经过测试,访问 mediaplay_do_url 时只需要设置 “支持重定向、设置 Referer” 即可。

image-20231020165557874.png

如下,我并没有使用多线程,我的主要目的是分析网站啦。

from pathlib import Path

def download_img(detail_do_data: dict[str, str], directory: Path):
    """下载图片到指定目录"""
    # 首先处理用于 media_do_url 请求所需的参数
    allImgResIdSigns = detail_do_data["allImgResIdSigns"].split(",")
    allImgResIds = detail_do_data["allImgResIds"]
    # 它们两个的长度应该相等,并且等于书籍的总页数
    assert len(allImgResIds) == len(allImgResIdSigns) == detail_do_data["pageCount"]

    # 输出一些书籍信息
    print("书籍名:", detail_do_data["bookName"])
    print("页数:", detail_do_data["pageCount"])

    # 开始准备下载图片
    mediaplay_do_url = "https://mp.bookln.cn/resourceservice/mediaplay.do?mediaType=3&resId={}&resIdSign={}"
    for i in range(detail_do_data["pageCount"]):
        # 生成链接,访问它需要重定向才能下载到图片
        url = mediaplay_do_url.format(allImgResIds[i], allImgResIdSigns[i])
        # 准备请求头
        h = NORMAL_HEADERS.copy()
        h.update({
            "Referer": BOOK_API_URL.format(detail_do_data["bookId"])
        })
        # 支持重定向
        r = SESSION.get(url, headers=h,allow_redirects=True)

        # 保存的文件名
        filename = directory / f"{i}.jpg"
        with filename.open("wb") as fp:
            fp.write(r.content)

        print(f"\r下载进度: {i}/ {detail_do_data['pageCount']}", end="", flush=True)

    print()

book_id = "385519"
visit_book_page(book_id)

# 从文件读取,因为之前已经获取了 detail_do_data 到本地啦
with open("detail_do_data.json", "r", encoding="utf8") as fp:
    detail_do_data = json.load(fp)

# 下载的图片放到脚本所在目录的 download 目录中
d = Path(__file__).parent / "download"
d.mkdir(exist_ok=True)

download_img(detail_do_data["data"], d)

此处没有展示下载的图片,见最后的展示。

好险!压缩图片

下载之后看了一下,居然有这么 400 MB 了!可恶!忘了这个了,后文将使用 pillow 库来压缩图片。

image-20231020204715265.png


# 简化的代码
r = SESSION.get(url, headers=h,allow_redirects=True)

# 保存的文件名
filename = directory / f"{i}.jpg"
im = Image.open(BytesIO(r.content))
# 进行压缩
im.save(filename, optimize=True, quality=60)

乘胜追击!生成带书签的 PDF

这是使用了 pythonimg2pdf 库将图片转成 PDF,然后使用 PyPDF2 库生成带书签的 PDF

其中书签信息从 detail_do_data["chapter"] 中获取,以下是简单分析:

{
    "authType": 0,
    "authVal": "0",
    "bookId": 385519,
    "chapterStatus": 1,
    "id": 2169992,
    "imgResIdSigns": "1a111a,89dbec",
    "imgResIds": "61074622,61074623",
    # 这是书签的层级,1 就是最顶级
    "level": 1,
    "name": "变形高温合金",
    "orders": 50000000,
    # 该书签所在的页面
    "pageNo": 15,

    # 如果有该属性,则含有子书签
    "sections": [
                    {
                        "authType": 0,
                        "authVal": "0",
                        "bookId": 385519,
                        "chapterStatus": 1,
                        "id": 2169993,
                        "imgResIdSigns": "0f6445,73bdcc,8a43a7,c7aac8",
                        "imgResIds": "61074624,61074625,61074626,61074627",

                        # 子书签的层级加 1 了哟
                        "level": 2,
                        "name": "GH4151合金Φ300mm均质细晶棒材制备技术",
                        "orders": 10000000,

                        # 对应的页面
                        "pageNo": 17,
                        "pid": 2169992
                    },
                ]

所以有以下代码。

def create_pdf(chapters:dict, directory:Path, PDFFilename: str):
    """
    从 chapter 中获取书签信息,
    然后将  directory 书签下的所有图片生成 PDF,其名为 PDFFilename
    """
    # 先获取所有 img,然后利用 img2pdf 生成一个 PDF
    # 确保 directory 书签下只有 .jpg,这里没有做判断哟
    imgs = list(directory.iterdir())
    # 还需要对它进行排序,因为文件名是 1.jpg 之类的
    # 默认情况下,1.jpg 后面反而是 10.jpg 了
    # f.stem 获取它的数值部分
    imgs.sort(key=lambda f:int(f.stem))
    # 然后转为文件名字符串
    imgs = [str(item) for item in imgs]

    # 将所有文件转为 PDF
    raw_pdf = img2pdf.convert(imgs)

    # 以下就是 PyPDF2 的用法了
    reader = PyPDF2.PdfReader(BytesIO(raw_pdf))
    writer = PyPDF2.PdfWriter()
    writer.append_pages_from_reader(reader)

    # 然后开始处理书签!
    # 因为处理书签用的 API add_outline_item 需要知道它的上一层级
    def add_outline_item(current_chapters, parent_outline):
        """  current_chapters 是特定层级下的所有书签的序列, parent_outline 则是它的上一层"""
        for current_chapter in current_chapters:
            title = current_chapter["name"] # 书签名
            page_no = current_chapter["pageNo"] - 1 # 书签所在的页数,从 0 开始,所以要减少 1

            current_outline = writer.add_outline_item(title, page_no, parent_outline)

            # 处理子书签
            if "sections" in current_chapter:
                add_outline_item(current_chapter["sections"], current_outline)

        pass

    # 开始遍历、添加书签,初始情况下顶层书签是 None 咯
    add_outline_item(chapters, None)

    # writer.close()  # 保存 PDF!!!
    writer.write(PDFFilename)

尾声

如下对全部代码进行了测试。

image-20231020213104162.png image-20231020213232892.png

所有代码已经上传到这里:https://wwvq.lanzouj.com/idc4S1ccbqsb

再次说明,我只简单整合了代码,并没有去优化哟,毕竟我的目的是分析网页啦。

另外要着重强调:我只测试了这一个书链的 ebook 链接,因为我不知道这个链接哪来的,所以没有其它的书籍链接来测试。

如果上述思路对某些 ebook 链接并没有用,这……概不负责哈。

留言

此次斗法之后将会闭关修炼,争取突破到练气二层。现在层次太低,不适合大量斗法,还是要夯实基础,脚踏实地修炼,精进法力、熟悉武技。

毕竟我没有逆天小瓶、也没有高人传法一夜突破到筑基的奇遇、更不是开局被未婚妻退婚的天选之子。

免费评分

参与人数 13吾爱币 +22 热心值 +13 收起 理由
x34178148 + 1 + 1 你写这么详细,你就是最棒的。
JewelyLilies + 1 + 1 用心讨论,共获提升!
zhu0598 + 1 + 1 谢谢@Thanks!
junjia215 + 1 + 1 用心讨论,共获提升!
ilovezll + 1 + 1 我很赞同!
wiltzy + 1 + 1 谢谢@Thanks!
sbham + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩! 不明觉厉
luolifu + 1 + 1 我很赞同!
wushaominkk + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
helian147 + 1 + 1 热心回复!
hrh123 + 1 + 1 用心讨论,共获提升!
山岚 + 3 + 1 我很赞同!
Eqwer + 2 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

poptop 发表于 2023-10-27 13:58
谢谢分享                                                                     
sub8 发表于 2024-8-2 14:51
书链上的书好像都是可以预览一部分内容,后面的不可以浏览。
七彩课堂九年级下册化学人教版七彩培优 https://mp.bookln.cn/book/sample.htm?code=F732d5e0734&_src=webapp
那么这样的书下载下来是全部页面么?期待大佬推出小软件。
正己 发表于 2023-10-20 23:12
Eqwer 发表于 2023-10-20 23:25
详细,太他妈详细了。打印,pdf,上传云盘备份,防止被删贴
liguanlin 发表于 2023-10-21 08:22
看着不错,加油!
云的彼岸918 发表于 2023-10-21 08:32
大佬牛逼!
ysjd22 发表于 2023-10-21 09:43
试图教会我??
xiaoshan208 发表于 2023-10-21 11:05
优秀,值得收藏,如果能整合成傻瓜式软件就更完美了
sina0022 发表于 2023-10-21 12:37
讲解详细,好好学习一下,感谢分享
凌云科技 发表于 2023-10-21 19:59
显然我看完后一脸懵,有没有大神做个成品看看啊
青花西岳 发表于 2023-10-21 22:46
值得收藏
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-22 01:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表