文章目录
1 核心需求
经常在手机上刷头条,自然免不了收藏一些不错的文章或视频。结果时间久了才发现已经收藏了几百条内容。但是由于手机端只能一条条下拉,查看之前的收藏很不方便,所以就有一个想法,可以从页面中提取自己的收藏的全部内容,并导出成一个CSV文件。在线查了下,头条果然是有网页版的,所以答案显然是可行的,本文就具体实现过程进行经验分享。
1.1 主要思想
主要思想是从头条我的收藏中提取以下信息 (title, date, type, url)
,具体包括以下过程。
- 从头条网页的收藏中导出html文件;
- 从HTML文件中解析出每个收藏的4类数据 (title, date, type, url),其中:
- 文章和视频 (title, date, type, url) 内容相同
- 问答 title=博主名, date=‘-’, type=‘wenda’
- 博主 title=博主名, date=最后更新日期, type=‘user’
- 将所有收藏信息 (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文件下载,具体过程如下。
- 打开我的收藏
首先打开头条官网,登陆以后右上角头像菜单点开收藏: - 手工保存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 参考资料
- xml 解析 http://t.zoukankan.com/fiona-zhong-p-9910548.html
- html 解析 https://blog.csdn.net/asd3331380/article/details/121893941
- Beautiful Soup Documentation https://beautiful-soup-4.readthedocs.io/en/latest/
函数文档 https://www.crummy.com/software/BeautifulSoup/bs4/doc/ - find_all 函数API: https://beautiful-soup-4.readthedocs.io/en/latest/#find-all
- 在线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("//p3.toutiaoimg.com/origin/pgc-image/b13dded4c4e948e293e217f95e8565b4");"></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>