数据采集实战(六)-- 新浪新闻

打印 上一主题 下一主题

主题 980|帖子 980|积分 2940

1. 概述

新闻是我们了解外界的重要渠道,以前,我们一般通过报纸和电视来获取新闻,那时候,获取新闻不仅有一定的成本,效率还不高。
而如今,获取新闻的途径太多太方便了,大量重复的新闻充斥着各大平台,获取新闻已经没有什么成本,问题变成了过滤和鉴别新闻的可信程度。
下面用 【新浪新闻】 作为采集对象,抛砖引玉,演示下新闻从采集到分析的整个过程。
2. 采集流程

主要流程分为4个步骤:

2.1 采集

从新浪滚动新闻页面中,找出获取新闻的API,然后,并发的采集新闻。
这里为了简单起见,主要采集了新闻标题和摘要信息。
  1. # -*- coding: utf-8 -*-
  2. import requests
  3. import csv
  4. import time
  5. import os
  6. import threading
  7. import math
  8. host = "https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page={}"
  9. class spiderThread(threading.Thread):
  10.     def __init__(self, fname, delay, start_page, end_page):
  11.         threading.Thread.__init__(self)
  12.         self.fname = fname
  13.         self.delay = delay
  14.         self.start_page = start_page
  15.         self.end_page = end_page
  16.     def run(self):
  17.         for page in range(self.start_page, self.end_page):
  18.             time.sleep(self.delay)
  19.             url = host.format(page)
  20.             rows = _parse_html_content(url)
  21.             _save_data(self.fname, rows)
  22.             print("thead: {} 已采集 第【{}】页的数据".format(self.fname, page))
  23. def spider(start_page=1, pages=50, concurrency=2):
  24.     """ 采集数据
  25.     并发采集 sina 滚动新闻数据
  26.     Parameters:
  27.       start_page - 采集开始的页数,默认从第1页开始采集
  28.       pages - 采集的页数,默认采集500页
  29.       concurrency - 并发采集的数量,默认4个线程采集
  30.     Returns:
  31.       采集结果写入文件
  32.     """
  33.     if pages < 0 or concurrency < 0:
  34.         print("pages or concurrency must more than 0")
  35.         return
  36.     threads = []
  37.     now_str = time.strftime("%Y-%m-%d--%H-%M-%S", time.localtime())
  38.     delta = math.ceil((pages - start_page + 1) / concurrency)
  39.     for i in range(concurrency):
  40.         end_page = delta + start_page
  41.         if end_page > pages:
  42.             end_page = pages + 1
  43.         t = spiderThread("{}-{}".format(now_str, i + 1), 2, start_page, end_page)
  44.         threads.append(t)
  45.         t.start()
  46.         start_page = end_page
  47.     for t in threads:
  48.         t.join()
  49.     print("采集结束")
  50. def _parse_html_content(url):
  51.     """ parse html to csv row like: oid, intime, title, media_name, intro
  52.     """
  53.     response = requests.get(url)
  54.     data = response.json()
  55.     data = data["result"]["data"]
  56.     rows = []
  57.     for d in data:
  58.         # print(d["oid"])
  59.         # print(d["intime"])
  60.         # print(d["title"])
  61.         # print(d["media_name"])
  62.         # print(d["intro"])
  63.         rows.append([d["oid"], d["intime"], d["title"], d["media_name"], d["intro"]])
  64.     return rows
  65. def _save_data(filename, rows):
  66.     fp = os.path.join("./data", filename + ".csv")
  67.     print("fname {}: rows {}".format(filename, len(rows)))
  68.     with open(fp, "a", encoding='utf-8') as f:
  69.         writer = csv.writer(f)
  70.         writer.writerows(rows)
复制代码
主要函数是:def spider(start_page=1, pages=50, concurrency=2)
可以设置采集的起始/结束页,以及并发采集的线程数。
新浪滚动新闻最多只提供了最近50页的新闻,大概4000多条新闻。
采集之后,默认会在 data文件夹下生成采集结果的 csv 文件(几个线程采集,就生成几个csv)
2.2 清理

清理数据主要将多线程采集的所有csv文件合并成一个,同时去重和按照时间排序。
  1. # -*- coding: utf-8 -*-
  2. import pandas as pd
  3. import os
  4. import time
  5. def clean(data_dir="./data"):
  6.     """ 清洗数据
  7.     合并所有采集的数据文件,去除重复数据和不需要的字段
  8.     Parameters:
  9.       data - 采集数据的文件夹
  10.     Returns:
  11.       清理后的结果写入文件
  12.     """
  13.     data = _read_all_data(data_dir)
  14.     data = _uniq_and_order(data)
  15.     _write_clean_data(data_dir, data)
  16. def _read_all_data(data_dir):
  17.     all_data = []
  18.     for f in os.listdir(data_dir):
  19.         fp = os.path.join(data_dir, f)
  20.         if os.path.isdir(fp):
  21.             continue
  22.         data = pd.read_csv(
  23.             fp, names=["oid", "intime", "title", "media_name", "intro"], header=None,
  24.         )
  25.         all_data.append(data)
  26.     return pd.concat(all_data, ignore_index=True)
  27. def _uniq_and_order(data):
  28.     data = data.drop_duplicates(keep="first")
  29.     data = data.drop(columns=["oid", "intime", "media_name", "intro"])
  30.     # data = data.sort_values(by=["intime"], ascending=False)
  31.     return data
  32. def _write_clean_data(data_dir, data):
  33.     clean_data_dir = os.path.join(data_dir, "clean")
  34.     if not os.path.exists(clean_data_dir):
  35.         os.makedirs(clean_data_dir)
  36.     now_str = time.strftime("%Y-%m-%d--%H-%M-%S", time.localtime())
  37.     data.to_csv(
  38.         os.path.join(clean_data_dir, now_str + ".csv"), index=False, header=None
  39.     )
复制代码
清理之后,所有新闻合并在一个文件中,只保留了【新闻标题】用来分析。
2.3 分词

新闻标题的文字是没有规律的,所以分析之前需要先分词。
  1. # -*- coding: utf-8 -*-
  2. import jieba.posseg as pseg
  3. import pandas as pd
  4. import os
  5. import time
  6. def split_word(fp, data_dir="./data"):
  7.     """对标题进行分词
  8.     Parameters:
  9.       data_dir - 采集数据的文件夹
  10.       fp - 待分词的文件
  11.     Returns:
  12.       分词后的结果写入文件
  13.     """
  14.     mdata = {}
  15.     with open(fp, "r", encoding="utf-8") as f:
  16.         total = len(f.readlines())
  17.         count = 0
  18.         f.seek(0)
  19.         for line in f:
  20.             count += 1
  21.             print("解析进度[{}/{}]...".format(count, total))
  22.             _jieba(line, mdata)
  23.     total = len(mdata)
  24.     count = 0
  25.     data_list = []
  26.     for word in mdata.keys():
  27.         count += 1
  28.         print("写入进度[{}/{}]...".format(count, total))
  29.         data_list.append([word, mdata[word][0], mdata[word][1]])
  30.     data = pd.DataFrame(data_list, columns=["单词", "词性", "数量"])
  31.     _write_split_data(data_dir, data)
  32. def _jieba(s, mdata):
  33.     words = pseg.cut(s, HMM=True)
  34.     for word, flag in words:
  35.         if _check_flag(flag):
  36.             if word not in mdata.keys():
  37.                 mdata[word] = [flag, 0]
  38.             mdata[word][1] += 1
  39.     return mdata
  40. def _write_split_data(data_dir, data):
  41.     split_data_dir = os.path.join(data_dir, "jieba")
  42.     if not os.path.exists(split_data_dir):
  43.         os.makedirs(split_data_dir)
  44.     now_str = time.strftime("%Y-%m-%d--%H-%M-%S", time.localtime())
  45.     data.to_csv(os.path.join(split_data_dir, now_str + ".csv"), index=False)
  46. def _check_flag(flag):
  47.     flags = ["n"]  # n-名词类,a-形容词类 v-动词类
  48.     for fg in flags:
  49.         if flag.startswith(fg):
  50.             return True
  51.     return False
复制代码
这里使用分词使用是 【结巴分词】库,并且只保留了新闻标题中的 【名词】。
2.4 分析绘图

最后是分析结果,为了快速获取新闻的有效信息,这一步必不可少。
这里只分析了【新闻标题】中的名词,所以这里就做了两张图,一张是高频词的柱状图,一张是词云图。
  1. # -*- coding: utf-8 -*-
  2. import pandas as pd
  3. import os
  4. import wordcloud
  5. from prettytable import PrettyTable
  6. import matplotlib
  7. import matplotlib.pyplot as plt
  8. # 为了显示中文
  9. matplotlib.rcParams["font.sans-serif"] = ["Microsoft YaHei Mono"]
  10. matplotlib.rcParams["axes.unicode_minus"] = False
  11. cn_font_path = "D:\\miniconda3\\envs\\databook\\Lib\\site-packages\\matplotlib\\mpl-data\\fonts\\ttf\\Microsoft-Yahei-Mono.ttf"
  12. def analysis(fp, data_dir="./data"):
  13.     """分析数据
  14.     根据分词结果分析结果
  15.     """
  16.     data = pd.read_csv(fp)
  17.     # 过滤关键词长度为1的数据
  18.     data = data[data["单词"].str.len() > 1]
  19.     # 创建文件夹
  20.     analy_data_dir = os.path.join(data_dir, "analy")
  21.     if not os.path.exists(analy_data_dir):
  22.         os.makedirs(analy_data_dir)
  23.     # 显示前N个关键词
  24.     N = 20
  25.     _topN_table(data, N)
  26.     # 高频词柱状图比较
  27.     _topN_bar_graph(data, N, analy_data_dir)
  28.     # 词云 图
  29.     _word_cloud(data, analy_data_dir)
  30. def _topN_table(data, n):
  31.     tbl = PrettyTable()
  32.     data = data.sort_values(by=["数量"], ascending=False)
  33.     tbl.field_names = data.columns.values.tolist()
  34.     tbl.add_rows(data.head(n).values.tolist())
  35.     print(tbl)
  36. def _topN_bar_graph(data, n, d):
  37.     data = data.sort_values(by=["数量"], ascending=False)
  38.     data = data.head(n)
  39.     y = list(data["数量"])
  40.     plt.bar(range(n), height=y, tick_label=range(1, n + 1), color=["b", "c", "g", "m"])
  41.     plt.xticks(range(n), data["单词"])
  42.     fig = plt.gcf()
  43.     fig.set_size_inches(15, 5)
  44.     plt.savefig(os.path.join(d, "bar.png"))
  45. def _word_cloud(data, d):
  46.     w = wordcloud.WordCloud(
  47.         width=800, height=600, background_color="white", font_path=cn_font_path
  48.     )
  49.     # 词频大于N的单词才展示
  50.     N = 5
  51.     data = data[data["数量"] > N]
  52.     dic = dict(zip(list(data["单词"]), list(data["数量"])))
  53.     w.generate_from_frequencies(dic)
  54.     w.to_file(os.path.join(d, "word_cloud.png"))
复制代码
注意,这里为了显示中文,我引入了自己的字体(Microsoft-Yahei-Mono.ttf),你也可以换成任何能够显示中文的字体。
3. 总结

其实,上面的功能主要就是找出新闻中的高频词,从而可以看看公众最近关注最多的是哪方面。
虽然简单,但是麻雀虽小五脏俱全,整个流程是完备的,有兴趣的话,每个流程中的细节可以继续丰富。
代码运行的最终结果如下:(运行时间:2022-07-29  中午12点多,新闻是不断更新的,不同时间采集的话,运行结果会不一样)


4. 注意事项

爬取数据只是为了研究学习使用,本文中的代码遵守:

  • 如果网站有 robots.txt,遵循其中的约定
  • 爬取速度模拟正常访问的速率,不增加服务器的负担
  • 只获取完全公开的数据,有可能涉及隐私的数据绝对不碰

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

兜兜零元

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表