# 数据爬虫的设计与实现
数据爬虫需要完成两个功能, 一是直接获取今日头条的数据, 二是在今日头条数据的基础上模拟用户阅读新闻.
# 技术栈
- Python
- Selenium 自动化测试工具
- Requests HTTP 工具包
# 数据爬取
依照实际需要, 系统将爬取 18 种不同类别的新闻, 每种类别下有 10 篇左右的新闻. 除此之外, 系统还将爬取新闻的作者信息, 每篇新闻下的评论, 评论的作者, 以及每则评论下的回复, 回复的作者.
而为了降低系统的复杂度, 每篇新闻最多只会爬取 20 则评论信息, 每则评论下最多会爬取 10 则回复.
在个人水平的限制下, 我所知的爬取数据的手段有两种:
- 一是直接在渲染好的页面的 DOM 树下获取, 这个速度比较慢, 可以使用 Selenium 这类工具来完成
- 另一种是组装请求头, 假装浏览器发送请求获取数据.
那么, 我们的这个爬虫需要的数据包括 文章, 文章的作者, 评论, 评论的作者, 回复, 回复的作者. 至于为什么不把其他的用户拉进来, 因为今日头条的用户数量过于庞大, 而且相互关联, 形成网状, 全部拉下来是不可能的. 而且相比于数据的数量, 我认为更重要的是数据的完整性和纯洁性. 这些数据被爬取下来后将被存储在一个 MySql 数据库中.
考虑到时间成本, 只对文章内容采用在 DOM 树下爬取的方式 (文章内容没法使用请求, 其实是可以的, 但是得益于头条的反扒机制, 这种方式是不稳定的), 其他都使用伪装请求的方式.
# 模拟用户行为
首先说明一下, 个人认为, 模拟用户行为不是一种多么理想的操作, 它可能会带来很多的副作用. 至于为什么要模拟用户行为, 是因为数据爬虫只能获取哪些用户评论了新闻, 哪些用户回复了新闻, 至于这篇新闻到底有多少人浏览过, 这就无从查起了. 而前面提到过, 每篇新闻最多只会有 20 则评论信息, 每则评论下最多只有 10 则回复. 如此一条新闻下的用户行为数据不会超过 2 * 200, 绝大部分的用户只会在我们的数据库中出现两次用户行为记录(一次阅读, 一次评论; 或一次阅读, 一次回复; 或一次阅读, 一次编辑), 这对推荐系统来说, 体验是挺差的.
# 模拟方式
核心就是 "随机", 另一个需要注意的是, 模拟的只有用户的阅读行为. 获取的评论或回复的用户, 作为幸运儿, 会被系统选中, 被阅读. 系统会先随机选择几个(一般是 3 个以内, 太多, 数据库可能吃不消) 新闻类别, 随后系统会使用一个 for 循环, 在每个类别新闻下选取随机数量的新闻 (一般是 40 篇以内).
# 随机选取 [1, 2] 数量的类别
rand_category_num = random.randint(1, 2)
rand_cates = random.sample(categories, rand_category_num)
for rand_cate in rand_cates:
# 获取同类别下随机数量的新闻 id 列表
result_list = self.__art_dao.get_same_category_art(art_mod.art_id, rand_cate)
if result_list is not None:
for back_art in result_list:
try:
# 更新相关的用户 新闻统计数据
self.__cus_dao.insert_cus_behavior(com_cus_mod.cus_id, back_art[1], 2, back_art[0], back_art[0])
self.__cus_dao.update_cus_feature(rand_cate, com_cus_mod.cus_id, update_num=1)
self.__art_dao.update_art_feature(6, back_art[0], art_mod.art_time)
except:
continue
2
3
4
5
6
7
8
9
10
11
12
13
14
15
利用这样的操作, 可以只用几次数据爬取便能获取大量的活跃用户.
# 副作用的考虑
考虑没有模拟数据的情况, 系统评价一篇新闻的质量通常有三个指标: 发布时间, 评论数量, 回复数量, 因为系统在计算新闻分数的时候, 给这三个参数比较大的权重, 所以一篇文章的质量基本由这三个参数说了算, 而且这也基本符合正常的情况: 评论回复多的文章, 浏览数量通常不低.
但是现在, 每篇文章可能会有上千次的用户浏览记录, 原先的三个参数对新闻分数的影响一下子成了忽略不计的东西, 这就破坏了新闻的真实特征. 一种可能的解决办法就是降低阅读权重, 让这四个参数平起平坐, 但是我并没有用这个操作.
另外一个有意思的问题是, 如果只是随机一个类别下的新闻, 一开始被记录到数据库中的新闻往往会得到更多的用户阅读数据, 一种不完美的解决办法是对随机阅读的新闻的时间做出限制, 比如只允许随机阅读距当前 10 天的新闻. 非常不幸的一点是, 系统使用的接口获取的新闻的时间跨度广到离谱, 在模拟数据的过程中会失去一部分新闻.
# 爬虫程序流程的简单说明
在开始的时候, 我计划使用 process 和 dao 这两层, 就像 Spring 中的 service 和 dao 一样. 在我以这种思路写了一遍之后, 我发现这样的操作就是脱裤子放屁. 于是, process 不再包裹 dao, 单纯地负责页面内容和请求内容的获取, 并填写 model 对象; dao 则负责所有与数据库操作相关的内容, 接收填写好的 model 对象.
Python 就是 Python, 不要总想着写成别的语言的样子, 对, 我说的就是你 Java .
# 程序数据处理顺序
- 新闻作者信息
- 新闻内容
- 评论作者
- 评论内容
- 评论作者模拟操作
- 回复作者
- 回复内容
- 回复作者模拟操作
← 数据库的设计与实现 用户系统的设计与实现 →