使用BeautifulSoup对头条收藏的HTML数据进行分析

1 核心需求

经常在手机上刷头条,自然免不了收藏一些不错的文章或视频。结果时间久了才发现已经收藏了几百条内容。但是由于手机端只能一条条下拉,查看之前的收藏很不方便,所以就有一个想法,可以从页面中提取自己的收藏的全部内容,并导出成一个CSV文件。在线查了下,头条果然是有网页版的,所以答案显然是可行的,本文就具体实现过程进行经验分享。

1.1 主要思想

主要思想是从头条我的收藏中提取以下信息 (title, date, type, url) ,具体包括以下过程。

  1. 从头条网页的收藏中导出html文件;
  2. 从HTML文件中解析出每个收藏的4类数据 (title, date, type, url),其中:
  • 文章和视频 (title, date, type, url) 内容相同
  • 问答 title=博主名, date=‘-’, type=‘wenda’
  • 博主 title=博主名, date=最后更新日期, type=‘user’
  1. 将所有收藏信息 (title, date, type, url) 输出至同一个CSV文件。

2.1 模型概述

  • 输入:favourite.html
    下载到本地的收藏网页的 html 文件。
  • 输出:result.csv
    包括 title, date, type, url 4列。
  • 算法:数据转换器
    从 favourite.html 中提取 title, date, type, url 信息并输出到 result.csv 文件。

2 实现过程

2.1 原始数据获取

原始HTML文件可能通过爬虫提取,但本由于原网站使用了动态的加载技术,爬取有难度,而且由于只需要下载本人的收藏,所以通过手工进行原始html文件下载,具体过程如下。

  1. 打开我的收藏
    首先打开头条官网,登陆以后右上角头像菜单点开收藏:
  2. 手工保存HTML文件
    手工拖动右侧滚动条直到最下方,将整个文件保存,会得到1个文件 头条.html 和 1个文件夹 头条_files,其中头条.html 就是我们要的内容,可以使用记事本打开,能看到里面包括所有的文本内容。

2.2 HTML数据解析

经测试,BeautifulSoup 可以对复杂的HTML数据进行表示,可使用文件读取函数加载:
soup= BeautifulSoup(open("xx.html", encoding='utf-8'), features="html.parser")
也可直接从文本加载:
soup= BeautifulSoup(html_str, features="html.parser")
其中,features 是解析器,bs4支持Python标准库, lxml HTML解析器, lxml XML解析器 和 html5lib,可以根据不同的输入文件和兼容性进行选择。

2.3 标签查询

使用 soup.find_all() 方法进行数据提取查询,其中重要的语句为两种:

  • soup.find_all('div')
    找到所有 div 标签
  • soup.find_all('div', class_='feed-card-footer-time-cmp')
    找到所有包含 class=‘feed-card-footer-time-cmp’ 的 div 标签
    注意: 由于 class 是关键字,所以要加下划线,其他像 id, herf 等不用加。

3 样本解析

文件 头条.html 包括四类内容,如下所示:

  • 视频: <div class="profile-normal-video-card-wrapper">
  • 文章: <div class="profile-article-card-wrapper">
  • 博主: <div class="profile-wtt-card-wrapper">
  • 问答: <div class="profile-wenda-card-wrapper">
    具体可参见附件的数据样本示例。

每类内容提取方法都不一样,具体参见相应代码
文章和视频类:

  • title, url: 在第1个标题 a 中

  • date: 在一对div中

  • 数据样本: data/toutiao_data_20221003.rar --解压–> C:\data\toutiao.html

  • 结果输出: 分别为 .\data\toutiao_20221003.csv
    具体实现过程代码参见附录1。

4 提取后数据

最终提取的为CSV文件,格式如下所示:

title,date,type,url
"程序员必知必会10大基础算法",2020年05月30日,article,https://www.toutiao.com/article/6832468975046099463/
"人脑是一台计算机吗?",2022年10月04日,article,https://www.toutiao.com/article/7149576260891443724/
"金庸告诉我们的:为什么你总和你上司解释不清?",2022年09月29日,article,https://www.toutiao.com/article/7148723751150977574/
"谁在发表中国学者的论文?",2022年09月23日,article,https://www.toutiao.com/article/7146506696658043422/
"80个管理模型超全大合集",2022年09月28日,article,https://www.toutiao.com/article/7148300767613649422/
...(略过若干行)...
"发明专利的撰写方法-技术交底书",2022年04月03日,video,https://www.toutiao.com/video/7082354886947373583/
"机器学习常见算法-支持向量机",2019年11月26日,video,https://www.toutiao.com/video/6761602701307413006/
"秒懂!神经网络(NN)",2022年03月04日,video,https://www.toutiao.com/video/7071051314184061448/
...(略过若干行)...
"嗨绵先生",-,wenda,https://www.toutiao.com/w/1745208802804750/
"LaTeX工作室",-,wenda,https://www.toutiao.com/w/1744810162876480/
"周少说",-,wenda,https://www.toutiao.com/w/1744230970607688/
"向上突围",-,wenda,https://www.toutiao.com/w/1742758287606796/
"爱科学的卫斯理",-,wenda,https://www.toutiao.com/w/1741232193452099/
...(略过若干行)...
"一个程序员的奋斗史",2020年03月03日,user,https://www.toutiao.com/c/user/token/MS4wLjABAAAATpiSTY2wU_yIyk-IrqS5qsxNlNy777ziQ_uNPAi-4fY/?source=mine_home
"梦幻勇敢的春风",2020年01月15日,user,https://www.toutiao.com/c/user/token/MS4wLjABAAAAzIO8Jf2Vdrij2Xdh53UDGT1pp0sEDajlJhOB8qcCqDU/?source=mine_home
"葛小波不见了",2020年07月10日,user,https://www.toutiao.com/c/user/token/MS4wLjABAAAAG3bk07eM_4RuGli0TBU8LksnnZ-dA77vzEAfzo0eRXI/?source=mine_home
"丹淅湖畔",2019年07月30日,user,https://www.toutiao.com/c/user/token/MS4wLjABAAAAPmdeRd-sWe6OSpnKOMTb--LpFQDRRXc2b5OetswN6IM/?source=mine_home

5 参考资料

  1. xml 解析 http://t.zoukankan.com/fiona-zhong-p-9910548.html
  2. html 解析 https://blog.csdn.net/asd3331380/article/details/121893941
  3. Beautiful Soup Documentation https://beautiful-soup-4.readthedocs.io/en/latest/
    函数文档 https://www.crummy.com/software/BeautifulSoup/bs4/doc/
  4. find_all 函数API: https://beautiful-soup-4.readthedocs.io/en/latest/#find-all
  5. 在线HTML格式化工具 https://www.qianbo.com.cn/Tool/Beautify/Html-Formatter.html

附录

附录1:源代码

# encoding=utf-8
import os, sys
from bs4 import BeautifulSoup
from datetime import datetime, timedelta

def format_date(date_str):
    ''' 由于日期有 10月04日, 2022年10月04日,3小时前 等格式所以需要进行格式化'''
    if date_str.count('年') > 0 and date_str.count('月') > 0 and date_str.count('日') > 0:
        pass
    elif date_str.count('年') == 0 and date_str.count('月') > 0 and date_str.count('日') > 0:
            date_str = '2022年' + date_str
    elif date_str.count('天前') > 0:
        date_str = f'{datetime.now() - timedelta(days=int(date_str[0]) + 1):%Y年%m月%d日}'
    else:
        date_str = f'{datetime.now():%Y年%m月%d日}'
    return date_str

def process_div(div):
    ''' 从文章或视频中提取 (title, date_str, url) '''
    title, date_str, url = '', format_date(''), ''
    if div.a.get('title'):       # 如果第1个 <a> 存在 title 属性则进行提取
        title = div.a.get('title')
        url = div.a.get('href')
    for sd in div.find_all('div', class_='feed-card-footer-time-cmp'):
            date_str = format_date(sd.string)
    return title, date_str, url

# html.parser是解析器,也可是lxml
soup= BeautifulSoup(open("c:/data/toutiao.html", encoding='utf-8'), features="html.parser")

# 结果列表,每个元素为 (title, date, url, type) ,数据类型均为 str
res = [] 

# 1. 文章列表 
divs = soup.find_all('div', class_='profile-article-card-wrapper')
for div in divs:
    title, date_str, url = process_div(div)
    if len(title) > 0:
        res.append((title, date_str, 'article', url))
print('total divs:', len(divs))

# 2. 视频列表 <div class="profile-normal-video-card-wrapper">  total divs: 72
divs = soup.find_all('div', class_='profile-normal-video-card-wrapper')
for div in divs:
    title, date_str, url = process_div(div)
    if len(title) > 0:
        res.append((title, date_str, 'video', url))
print('total divs:', len(divs))


# 3. 问答列表 <div class="profile-wtt-card-wrapper"> total divs: 77
divs = soup.find_all('div', class_='profile-wtt-card-wrapper')
for div in divs:
    title = div.span.string
    url = div.a['href']
    if len(title) > 0 and len(url) > 0:
        res.append((title, '-', 'wenda', url))
print('total divs:', len(divs))

# 4. 博主列表 <div class="profile-wenda-card-wrapper"> total divs: 14
divs = find_div(soup, "profile-wenda-card-wrapper")
for div in divs:
    title = div.span.string
    url = div.a['href'] 
    date_str = '-'
    r1 = div.find_all('div', class_='feed-card-footer-time-cmp')
    if len(r1) > 0:
        date_str = format_date(r1[0].string)
    if len(title) > 0 and len(url) > 0:
        res.append((title, date_str, 'user', url))
print('total divs:', len(divs))


# 保存结果到CSV文件
csv_file = os.path.join(os.path.dirname(sys.argv[0]), 'data', 'toutiao_20221003.csv')
with open(csv_file,'w', encoding='utf-8') as fp:
    fp.write('title,date,type,url\n')
    for row in res:
        title, date, ctype, url = row
        title = title.replace('"', "'")
        fp.write(f'"{title}",{date},{ctype},{url}\n')
    print(f'Save data to "{csv_file}"')

附录2:收藏数据四类样本示例

头条的收藏分为文章、视频、问答和博主四类数据,以下分别显示了这四类数据的HTML格式的样本。

文章

<div class="profile-article-card-wrapper">
  <div class="feed-card-wrapper feed-card-article-wrapper">
    <div class="feed-card-article single-cover">
      <div class="feed-card-article-r">
        <div class="feed-card-cover">
          <a href="https://www.toutiao.com/article/6783165227505549827/" target="_blank" rel="noopener" title="清华大佬告诉你如何使用Python:高效操作文件的三个建议" aria-hidden="false" tabindex="0">
            <img src="./路过2020的头条主页 - 头条(www.toutiao_files/3114e9b2f8c646f885c4099c42b465ca_720x380_cs.webp" alt="">
          </a>
        </div>
      </div>
      <div class="feed-card-article-l">
        <a href="https://www.toutiao.com/article/6783165227505549827/" target="_blank" rel="noopener" class="title" aria-label="清华大佬告诉你如何使用Python:高效操作文件的三个建议">清华大佬告诉你如何使用Python:高效操作文件的三个建议</a>
        <div class="feed-card-footer-cmp">
          <div class="left-tools">
            <div class="profile-feed-card-tools-text">1万阅读</div>
            <div class="feed-card-footer-comment-cmp">
              <a href="https://www.toutiao.com/article/6783165227505549827/#comment" target="_blank" rel="noopener nofollow" aria-label="评论数63">63评论</a>
            </div>
            <div class="feed-card-footer-time-cmp">2020年01月19日</div>
          </div>
          <div class="right-tools">
            <div tabindex="0" role="button" aria-haspopup="true" aria-expanded="false" style="display: inline-block;">
              <div class="profile-feed-card-tools-actions">
                <i></i>
                <div class="actions-list-wrapper">
                  <div class="actions-list" role="menu">
                    <div class="action-item" role="menuitem" tabindex="-1">取消收藏</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="profile-wtt-card-wrapper">
  <div class="feed-card-wrapper feed-card-wtt-wrapper">
    <div class="feed-card-wtt single-cover">
      <div class="feed-card-wtt-r">

视频

<div class="profile-normal-video-card-wrapper">
  <div class="profile-normal-video-card">
    <div class="feed-card-video-single">
      <div class="r-content">
        <div class="feed-video-item">
          <div class="feed-card-cover">
            <a href="https://www.toutiao.com/video/6913739482567016973/" target="_blank" rel="noopener" title="超级赛亚人1到超级赛亚人100全形态,赛亚人是没有极限的" aria-hidden="false" tabindex="0">
              <img src="./路过2020的头条主页 - 头条(www.toutiao_files/bf98f4c829ec4ffb8fa612e5a69847e8_tplv-tt-svzoom-v3_1280_720.jpeg" alt="">
            </a>
            <div class="video-placeholder">
              <i></i>
              <span class="duration">05:38</span>
            </div>
          </div>
        </div>
      </div>
      <div class="l-content">
        <a href="https://www.toutiao.com/video/6913739482567016973/" target="_blank" rel="noopener nofollow" class="title">超级赛亚人1到超级赛亚人100全形态,赛亚人是没有极限的</a>
        <div class="footer">
          <div class="feed-card-footer-cmp">
            <div class="left-tools">
              <div class="profile-feed-card-tools-text">24万播放</div>
              <div class="feed-card-footer-comment-cmp">
                <a href="https://www.toutiao.com/video/6913739482567016973/" target="_blank" rel="noopener nofollow" aria-label="评论数432">432评论</a>
              </div>
              <div class="feed-card-footer-time-cmp">2021年01月04日</div>
            </div>
            <div class="right-tools">
              <div tabindex="0" role="button" aria-haspopup="true" aria-expanded="false" style="display: inline-block;">
                <div class="profile-feed-card-tools-actions">
                  <i></i>
                  <div class="actions-list-wrapper">
                    <div class="actions-list" role="menu">
                      <div class="action-item" role="menuitem" tabindex="-1">取消收藏</div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

博主信息

<div class="profile-wenda-card-wrapper">
    <div class="profile-wenda-card single-cover">
      <div class="inner">
        <div class="user-info">
          <a href="https://www.toutiao.com/c/user/token/MS4wLjABAAAAJ0zZHzdJsyMAT7Aia0LrJLOwHaGmW2YZwrJf3jGl4k8/?source=mine_home" rel="noopener nofollow" target="_blank">
            <div class="ttp-avatar auth-none">
              <img alt="" src="./路过2020的头条主页 - 头条(www.toutiao_files/2x_47f74e540c8814cf09ab32f2be7d2865_300x300.image" />
            </div>
            <span class="name">简道云</span>
          </a>
          <p>回答了问题</p>
        </div>
        <a class="title" href="https://www.toutiao.com/answer/7121996324019290383/" rel="noopener" target="_blank">
          <h2>怎样才能看出来一个人的工作能力到底强不强?</h2>
        </a>
        <div class="body">
          <div class="wenda-l">
            <a href="https://www.toutiao.com/answer/7121996324019290383/" rel="noopener" target="_blank">
              <p class="answer-content">在职场中,判断一个人工作能力的强弱,... 关注简道云,获取更多干货!</p>
            </a>
            <div class="feed-card-footer-cmp">
              <div class="left-tools">
                <div class="profile-feed-card-tools-text">415万展现</div>
                <div class="feed-card-footer-time-cmp">07月20日</div>
              </div>
              <div class="right-tools">
                <div aria-expanded="false" aria-haspopup="true" role="button" style="display: inline-block;" tabindex="0">
                  <div class="profile-feed-card-tools-actions">
                    <i></i>
                    <div class="actions-list-wrapper">
                      <div class="actions-list" role="menu">
                        <div class="action-item" role="menuitem" tabindex="-1">取消收藏</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="wenda-r">
            <div class="feed-card-cover">
              <a aria-hidden="true" href="https://www.toutiao.com/answer/7121996324019290383/" rel="noopener" tabindex="-1" target="_blank">
                <img alt="" src="./路过2020的头条主页 - 头条(www.toutiao_files/6695021ee0ed4e49a5c6bfd8cf1ee7de_960x0.jpeg" />
              </a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

问答信息

<div class="profile-wtt-card-wrapper">
  <div class="feed-card-wrapper feed-card-wtt-wrapper">
    <div class="feed-card-wtt single-cover">
      <div class="feed-card-wtt-r">
        <div class="feed-card-cover">
          <a href="https://www.toutiao.com/w/1668342094456832/" target="_blank" rel="noopener" aria-hidden="true" tabindex="-1">
            <img src="./路过2020的头条主页 - 头条(www.toutiao_files/5a630fc8626f45cc910ad6a521038c4c_tplv-shrink_1024_682.jpeg" alt="">
          </a>
        </div>
      </div>
      <div class="feed-card-wtt-l">
        <div class="feed-card-wtt-header">
          <div class="feed-card-wtt-user-info">
            <a href="https://www.toutiao.com/c/user/token/MS4wLjABAAAA1T-mU7aJrG1qOayYLTBQrAfBZL05nDXu0YgtbivY8UFln8ufsgxyO8bUA-pwI_45/?source=undefined" target="_blank" rel="noopener nofollow" title="非著名摄影爱好者">
              <img aria-hidden="true" src="./路过2020的头条主页 - 头条(www.toutiao_files/6322a7d1d4238236b3c055927073c744">
              <span>非著名摄影爱好者</span>
            </a>
          </div>
          <i class="verified-icon" style="width: 16px; height: 16px; background-image: url(&quot;//p3.toutiaoimg.com/origin/pgc-image/b13dded4c4e948e293e217f95e8565b4&quot;);"></i>
          <div class="time">2020年06月02日</div>
          <div class="dot">·</div>
          <div class="user-auth-desc" title="优质摄影领域创作者">优质摄影领域创作者</div>
        </div>
        <p class="content">
          <a href="https://www.toutiao.com/w/1668342094456832/" target="_blank" rel="noopener">1988年的北京街头纪实。摄影:约根森 ​​​</a>
        </p>
      </div>
      <div class="feed-card-wtt-footer">
        <div class="feed-card-footer-cmp">
          <div class="left-tools">
            <div class="feed-card-footer-share-cmp">
              <div class="ttp-interact-share">
                <div class="share-btn" tabindex="0" role="button" aria-haspopup="true" aria-expanded="false">分享</div>
                <ul class="share-tools panel-right panel-right-top" role="menu">
                  <li>
                    <div class="ttp-interact-item wtt icon-wtt" role="menuitem" tabindex="-1">转发到头条</div>
                  </li>
                  <li>
                    <div class="ttp-interact-item copy icon-copy" role="menuitem" tabindex="-1">复制链接</div>
                  </li>
                  <li>
                    <div class="ttp-interact-wx-wrapper">
                      <div aria-label="微信扫码分享" class="ttp-interact-item wx icon-wx" role="menuitem" tabindex="-1">微信</div>
                      <div class="qrcode-panel" style="display: none;">
                        <div id="_qrcode"></div>
                        <span>微信扫码分享</span>
                      </div>
                    </div>
                  </li>
                  <li>
                    <div class="ttp-interact-item weibo icon-weibo" role="menuitem" tabindex="-1">新浪微博</div>
                  </li>
                  <li>
                    <div class="ttp-interact-item qzone icon-qzone" role="menuitem" tabindex="-1">QQ空间</div>
                  </li>
                </ul>
              </div>
            </div>
            <div class="feed-card-footer-comment-cmp style-2">
              <a href="https://www.toutiao.com/w/1668342094456832/#comment" target="_blank" rel="noopener nofollow" aria-label="评论数47">
                <i></i>47
              </a>
            </div>
            <div class="feed-card-footer-like-cmp">
              <button type="button" aria-label="402点赞" aria-pressed="true" class="inner liked">
                <i></i>
                <span>402</span>
              </button>
            </div>
          </div>
          <div class="right-tools">
            <div class="profile-feed-card-tools-text">48万展现</div>
            <div tabindex="0" role="button" aria-haspopup="true" aria-expanded="false" style="display: inline-block;">
              <div class="profile-feed-card-tools-actions">
                <i></i>
                <div class="actions-list-wrapper">
                  <div class="actions-list" role="menu">
                    <div class="action-item" role="menuitem" tabindex="-1">取消收藏</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>  
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值