V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
cloudBird
V2EX  ›  Python

给你的个人微信朋友圈数据生成一本电子书吧!

  •  
  •   cloudBird ·
    shengqiangzhang · 2019-06-10 07:19:58 +08:00 · 6910 次点击
    这是一个创建于 2027 天前的主题,其中的信息可能已经有所发展或是发生改变。

    给你的个人微信朋友圈数据生成一本电子书吧!

    简介

    微信朋友圈保留着你的数据,它留住了美好的回忆,记录了我们成长的点点滴滴。发朋友圈从某种意义上来讲是在记录生活,感受生活,并从中看到了每个人每一步的成长。

    这么一份珍贵的记忆,何不将它保存下来呢?只需一杯咖啡的时间,即可一键打印你的朋友圈。它可以是纸质书,也可以是电子书,可以长久保存,比洗照片好,又有时间足迹记忆。

    • 这本书,可以用来:
    • 送给孩子的生日礼物
    • 送给伴侣的生日礼物
    • 送给未来的自己
    • ……

    现在,你可以选择打印电子书或者纸质书。打印纸质书的话,可以找第三方机构花钱购买;打印电子书的话,我们完全可以自己动手生成,这可以省下一笔不小的开支

    部分截图

    在开始写代码思路之前,我们先看看最终生成的效果。

    电子书效果

    纸质书效果

    代码思路

    获取微信书链接

    看完效果图之后,开始进入代码编写部分。首先,由于朋友圈数据的隐私性较高,手动获取的话,需要使用 root 的安卓手机进行解密或对 pc 端备份的聊天记录数据库进行解密,这对大部分人来说难度较大。所以我们采取的思路是基于现有的数据进行打印电子书。

    目前,已经有第三方服务支持导出朋友圈数据,微信公众号 [出书啦] 就提供了这样一种服务。这种服务很大可能性是基于安卓模拟器进行自动化采取操作的,具体就不详细讲了。

    首先,关注该公众号,然后开始制作微信书。该过程为小编添加你为好友,然后你将朋友圈开放给他看,等一会后采集完毕后,小编会发给你一个专属链接,这个链接里面的内容就是你的个人朋友圈数据。

    生成电子书

    有了这个链接后,我们开始对该页面的内容进行打印。

    整个过程基于 selenium 自动化操作,如果你有了解过 selenium 的话,那么其实该过程是很简单的。

    首先,引导用户输入微信书链接,我们采用在浏览器弹出一个输入文本框的形式让用户输入数据。 首先,在 selenium 中执行 js 代码,js 代码中完成弹出输入文本框的功能。

    输入微信书链接

    # 以网页输入文本框形式提示用户输入 url 地址
    def input_url():
        # js 脚本
        random_id = [str(random.randint(0, 9)) for i in range(0,10)]
        random_id = "".join(random_id)
        random_id = 'id_input_target_url_' + random_id
        js = """
            // 弹出文本输入框,输入微信书的完整链接地址
            target_url = prompt("请输入微信书的完整链接地址","https://");
            // 动态创建一个 input 元素
            input_target_url = document.createElement("input");
            // 为其设置 id,以便在程序中能够获取到它的值
            input_target_url.id = "id_input_target_url";
            // 插入到当前网页中
            document.getElementsByTagName("body")[0].appendChild(input_target_url);
            // 设置不可见
            document.getElementById("id_input_target_url").style.display = 'none';
            // 设置 value 为 target_url 的值
            document.getElementById("id_input_target_url").value = target_url
        """
        js = js.replace('id_input_target_url', random_id)
    
        # 执行以上 js 脚本
        driver.execute_script(js)
    
    

    上述 js 代码的具体步骤为:弹出一个输入文本框,创建一个动态元素,随机命名该元素的 id,并将这个动态元素插入到当前页面中,使得可以在 python 中通过 selenium 获取到输入文本框的内容。

    接着,在 selenium 中检测是否存在该弹框,如果不存在则获取该弹框的内容,并进行后续步骤,该过程代码如下:

    # 执行以上 js 脚本
    driver.execute_script(js)
    # 判断弹出框是否存在
    while(True):
        try:
            # 检测是否存在弹出框
            alert = driver.switch_to.alert
            time.sleep(0.5)
        except:
            # 如果抛异常,说明当前页面不存在弹出框,即用户点击了取消或者确定
            break
    # 获取用户输入的链接地址
    target_url = WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.ID, random_id)))
    value = target_url.get_attribute('value')
    # 删除空格
    value = value.strip() 
    

    至此,value的值即为弹出框返回的内容。(你可能会问,直接另 value=微信书链接不就可以了吗?事实上确实可以 ><|||,但是采用上述方式会有一个良好的交互效果,同时可以加深一下对 selenium 的了解程度^^)

    设置浏览器参数

    当用户输入链接完毕后,开始对浏览器进行初始化设置。首先设置chromedriver路径,可输入绝对路径或者相对路径,./表示当前目录下。不同系统和不同 chrome 版本需要下载不同的 chromedriver,请下载合适自己的版本,chromedriver 下载地址http://chromedriver.chromium.org/

    接着,设置自动打印成 pdf,这样就可以默认打印成 pdf 了,省得我们手动打印,该步骤代码如下:

    appState = {
        # 添加保存为 pdf 选项
        "recentDestinations": [
            {
                "id": "Save as PDF",
                "origin": "local",
                 "account":""
            }
        ],
        # 选择保存为 pdf 选项
        "selectedDestinationId": "Save as PDF",
        # 版本 2
        "version": 2,
        # 不显示页眉页脚
        "isHeaderFooterEnabled": False
    }
    

    同时,设置自动打印模式,该步骤代码如下:

    profile = {
        # 打印前置参数
        'printing.print_preview_sticky_settings.appState': json.dumps(appState),
        # 默认下载、打印保存路径
        'savefile.default_directory': os.getcwd()
    }
    

    通过这两步,就实现了全自动打印效果。

    分析网页元素

    接下来到了最关键的步骤,即分析网页元素。这个步骤我们可以顺便学习下基本的 css,js 知识。

    首先,按 F12 打开网页调试工具,对页面上不必要的元素进行隐藏

    我们可以看到,顶部的导航栏可能会影响打印效果,所以,我们将它隐藏。在调试工具中,选择 Copy Selector,得到返回的数据为body > header,通过 selenium 隐藏该元素的代码如下:

    # 隐藏导航栏,防止影响截图效果
    js = 'document.querySelector("body > header").style.display="none";'
    driver.execute_script(js)
    

    我们又发现,当前页面显示的数据只包含某个月朋友圈的数据,而不是所有朋友圈数据,那么如何显示出所有朋友圈数据呢?通过分析可知,当点击“下一月”按钮后,会有新的元素显示,而原来的元素被隐藏,而被隐藏的元素就是前面月份的数据。所以我们只要遍历到最后一个月后,把前面所有元素显示出来再打印就 OK 了。那么,如何判断是最后一个月呢?我们通过分析又可知,当不是最后一个月时,“下一月”的 class 名为next-month,而当在最后一月时,“下一月”的 class 名为next-month disable,因此我们可以检测它的 class 名进而知道是否处于最后一个月。该步骤代码如下:

    # 判断当下一月控件的 class name 是否为 next-month disable,如果是,则说明翻到最后一月了
    page_source = driver.page_source
    
    # 每一个 element 代表每一页,将每一页中 style 的 display 属性改成 block,即可见状态
    for index, element in enumerate(element_left_list):
        # ..在 xpath 中表示上一级的元素,也就是父元素
        parent_element = element.find_element_by_xpath('..')
        # 获取这个父元素的完整 id
        parent_element_id = parent_element.get_attribute('id')
    
        # 将该父元素更改为可见状态
        js = 'document.getElementById("{}").style.display="block";'.format(parent_element_id)
        driver.execute_script(js)
    

    但是,这样会出现一个问题,即使我们成功打印了,但是我们不难保证页面上的元素全部加载完成了,所以可能导致打印后某些元素没有显示出来,导致不是非常好看。因此,需要判断何时加载结束。

    通过分析我们得知,当网页元素没加载完毕时,会有一个“ loading ”提示,当网页元素加载完毕后,该元素隐藏起来了。因此,我们可以判断该元素是否隐藏来得知当前页面元素是否加载完毕。该部分代码如下:

    # 等待当前页面所有数据加载完毕,正常情况下数据加载完毕后,这个‘加载中’元素会隐藏起来
    while (True):
        loading_status = WebDriverWait(driver, 20).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, 'div.j-save-popup.save-popup')))
        if (loading_status.is_displayed() == False):
            break
    

    可是,我们又发现,及时等待网页元素加载完毕了,还是有部分图片没有显示出来。

    这就纳闷了,是为什么呢?通过分析我们又得知,这些图片处于加载状态的时候,class 名为lazy-img,通过字面意思,我们大概可以猜得出它是懒加载的意思,也就是用户滑动页面到那里时才进行加载,以便节省服务器压力。

    所以我们可以通过滑动到每一个 class 名为lazy-img的元素,使得它进行加载。那么?一个合适的方法就是,通过 js 定位到该元素,直到所有 class 名为lazy-img的元素不存在。

    while(True):
        try:
            lazy_img = driver.find_elements_by_css_selector('img.lazy-img')
            js = 'document.getElementsByClassName("lazy-img")[0].scrollIntoView();'
            driver.execute_script(js)
            time.sleep(3)
        except:
            # 找不到控件 img.lazy-img,所以退出循环
            break
    

    其中,document.getElementsByClassName("lazy-img")[0]指的是document.getElementsByClassName("lazy-img")的第一个元素,scrollIntoView()指的是滚动到该元素的位置

    打印电子书

    通过上述步骤,我们已经成功地隐藏部分可能会影响外观的元素,同时也显示所有所需的元素,接下来,就差打印部分了。可以直接通过 js 代码唤起浏览器打印功能,并且,之前我们已经设置为自动打印 pdf 格式了,所以它将自动打印为 pdf。但是,打印到哪里呢?这里需要设置下浏览器默认存储位置,保存的位置为当前目录。该步骤代码如下:

    # 默认下载、打印保存路径
    'savefile.default_directory': os.getcwd()
    
    # 调用 chrome 打印功能
    driver.execute_script('window.print();')
    

    打印完成后,设置退出浏览器driver.quit()

    经过测试,该电子书为超清版本,大小约16MB,所以质量还算不错的。

    补充

    完整版源代码存放在github上,有需要的可以下载

    41 条回复    2019-06-11 14:16:09 +08:00
    hellotao
        1
    hellotao  
       2019-06-10 07:33:48 +08:00 via Android
    这个创意不错
    NETBB
        2
    NETBB  
       2019-06-10 08:03:13 +08:00 via Android
    赞&mark 一下,学习
    good1uck
        3
    good1uck  
       2019-06-10 08:12:11 +08:00 via Android
    想法不错的
    good1uck
        4
    good1uck  
       2019-06-10 08:13:43 +08:00 via Android   ❤️ 1
    抖个机灵
    《帮娃投票索引》
    《微商货物清单》
    《中老年养身指南》
    BCy66drFCvk1Ou87
        5
    BCy66drFCvk1Ou87  
       2019-06-10 08:18:36 +08:00 via Android
    nice
    winterx
        6
    winterx  
       2019-06-10 08:18:49 +08:00   ❤️ 1
    创意不错,战略性 mark
    对我这种一年不发几次朋友圈的人来说可能有点浪费
    cedoo22
        7
    cedoo22  
       2019-06-10 09:38:10 +08:00
    创意不错,战略性 mark
    justfortest
        8
    justfortest  
       2019-06-10 09:49:21 +08:00 via iPhone
    有意思
    gaigechunfeng
        9
    gaigechunfeng  
       2019-06-10 09:50:15 +08:00
    火钳刘明,以后应该用的到
    JackieChoi
        10
    JackieChoi  
       2019-06-10 09:52:00 +08:00
    以前朋友圈发的都不忍直视。。
    wangxiaoaer
        11
    wangxiaoaer  
       2019-06-10 09:52:07 +08:00
    楼主提到了“出书了”这个公众号,那么跟 “出书了” 有什么区别吗?
    uloveznq
        12
    uloveznq  
       2019-06-10 09:53:56 +08:00
    赞啊!
    wxl1380610
        13
    wxl1380610  
       2019-06-10 10:01:52 +08:00
    不错 不错 战略性 mark
    jixia
        14
    jixia  
       2019-06-10 10:19:02 +08:00
    战略插眼,会用到
    cosven
        15
    cosven  
       2019-06-10 10:22:25 +08:00
    我记得以前有个学长创业,就是搞得这个,朋友圈 -> 纪念册 一条龙服务。
    Zoomgg
        16
    Zoomgg  
       2019-06-10 10:36:37 +08:00 via Android
    战略插眼
    chantan
        17
    chantan  
       2019-06-10 10:55:38 +08:00 via iPhone
    uTuw2C6uf964Kx6o
        18
    uTuw2C6uf964Kx6o  
       2019-06-10 11:00:24 +08:00   ❤️ 1
    公众号: 时光书、心书,这些已经成立好久了,专门做微信书等的,很成熟了
    aino
        19
    aino  
       2019-06-10 11:18:01 +08:00
    有没有 QQ 出书的啊
    coder1
        20
    coder1  
       2019-06-10 14:19:57 +08:00
    @aino QQ 应该更简单,毕竟浏览器可访问的
    moxiaonai
        21
    moxiaonai  
       2019-06-10 14:22:19 +08:00 via Android
    马克
    telami
        22
    telami  
       2019-06-10 14:22:42 +08:00
    感谢楼主,本地 pdf 已经生成,就是好多图片都被截断了,一部分在上一页,一部分在下一页,不知道能不能优化一下
    WhoCanBeRich
        23
    WhoCanBeRich  
       2019-06-10 14:41:20 +08:00
    太棒了 收藏收藏 另外楼主的问题能完善一下吗
    encro
        24
    encro  
       2019-06-10 14:45:25 +08:00
    @aino @mrant @telami 谢谢,正巧看到这个,我司公众号“拾光书”,支持 QQ 的日志导出成书。
    aino
        25
    aino  
       2019-06-10 15:12:30 +08:00
    @encro #24 没看见 QQ 的
    cjh1095358798
        26
    cjh1095358798  
       2019-06-10 15:36:22 +08:00 via Android
    收藏,战略性马克
    Beebird
        27
    Beebird  
       2019-06-10 16:13:56 +08:00
    @cloudBird 楼主的创新精神可嘉,我看到这个帖子马上分享给了我的朋友,心书(公众号:心书)的创始人之一。朋友初看之下也很是赞赏,但随后敏锐地发现楼主使用的效果图来自心书,恐怕有盗图之嫌。

    请注意我的截图中右上角红色方框内不完整的“心"字,这正是心书的袋子上的 Logo。 代码我朋友还未细看,我个人猜测楼主并无恶意。



    PS: 楼主的 ID 和我有点像,我朋友还以为是我的马甲,我更加必须站出来指正楼主的问题了。
    Beebird
        28
    Beebird  
       2019-06-10 16:28:37 +08:00
    cloudBird
        29
    cloudBird  
    OP
       2019-06-10 17:58:20 +08:00
    @Beebird 非常抱歉,一开始我是通过其他渠道了解到[出书啦]的,所以就对其进行分析。在写文章的时候发现[出书啦]没有比较好看的样图,所以我就在网上搜索到其他类似的微信号,并找到[心书]。我发现[新书]上面的客户晒单图比较好看,所以就借用了。由于 V2EX 无法编辑帖子了,所以我会在 github 注明来源。

    图片引用自: https://weixinshu.com/library/unboxing
    yawn852
        30
    yawn852  
       2019-06-10 18:15:38 +08:00 via iPhone
    马克
    itqls
        31
    itqls  
       2019-06-10 18:17:10 +08:00
    我的是无字天书
    warkbox
        32
    warkbox  
       2019-06-10 18:21:53 +08:00
    @itqls 我笑死
    littlezhan
        33
    littlezhan  
       2019-06-10 18:29:03 +08:00
    感觉不错!支持
    cloudBird
        34
    cloudBird  
    OP
       2019-06-10 18:41:44 +08:00
    @itqls 可能你没有朋友圈动态吧~~~
    telami
        35
    telami  
       2019-06-10 21:42:51 +08:00   ❤️ 1
    @Beebird 你家这个排版好看
    Heanes
        36
    Heanes  
       2019-06-11 00:33:50 +08:00
    创意 nice
    ww2000e
        37
    ww2000e  
       2019-06-11 08:23:30 +08:00
    不错。。不过我不发朋友圈
    Beebird
        38
    Beebird  
       2019-06-11 10:06:47 +08:00
    @cloudBird 理解
    hellolex
        39
    hellolex  
       2019-06-11 11:47:41 +08:00
    这个想法我也一直有,我的目的是想备份下自己的朋友圈,但是难点难道不是获取朋友圈数据吗,我还没发现什么好的 api 能把自己的朋友圈数据完整的导出来
    wangsongyan
        40
    wangsongyan  
       2019-06-11 11:49:22 +08:00
    @chantan 请问这是个什么软件?
    solxnp
        41
    solxnp  
       2019-06-11 14:16:09 +08:00
    战略性收藏 日后需要的时候用
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1206 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 23:23 · PVG 07:23 · LAX 15:23 · JFK 18:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.