任务 2 新闻数据获取及保存
任务目的
这一步的目标是实现新闻数据的爬取,并将获取到的新闻数据保存在本地文件中。这里需要用到requests和lxml模块,前者用于获取页面内容,后者用于对页面中的关键信息进行提取,最终保存提取到的所有文本内容。
任务步骤
1.新闻数据提取代码构建
创建新闻数据提取文件get_news.py
。
参考 任务1 最后新建main.py
的操作,在项目目录中执行下方命令,创建获取新闻数据的get_news.py
文件:
vim get_news.py
按下I
键进入编辑模式,复制下方的代码,粘贴到文件中:
import random
import time
from urllib.parse import urljoin
import requests
from lxml.etree import HTML
# 指定要爬取的初始页面地址
BASE_URL = "https://cn.chinadaily.com.cn/"
def get_content_by_xpath(url, xpath):
"""根据URL地址和XPath提取匹配内容"""
# 定义请求头
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
# 发送get请求并接收数据
resp = requests.get(url=url, headers=headers)
# 将接收到的字符串数据转换成XPath解析对象
html_tree1 = HTML(resp.content)
# 根据XPath指令返回匹配的内容
content = html_tree1.xpath(xpath)
return content
def save_top_news(num=10, filename="top_news_content.txt"):
"""保存最近的n篇新闻"""
# 从中国日报主页获取疫情专区的URL
# //div[@class='tou-right']/a/@href
resp1 = get_content_by_xpath(url=BASE_URL, xpath="//div[@class='tou-right']/a/@href")
static_url = "//cn.chinadaily.com.cn/a/202001/31/WS5e33aa27a3107bb6b579c52f.html"
target_url_part1 = resp1[0] if resp1[0] == static_url else static_url
# 结合初始页面,拼接出疫情专题页面的完整地址
target_url1 = urljoin(BASE_URL, target_url_part1)
# 从疫情专区获取最新的num篇新闻报道URL
# //div[@class='eve'][1]//div[@class='ove']/div[@class='ove-right']/h1/a/@href
url_list = get_content_by_xpath(url=target_url1, xpath="//div[@class='eve']//div[@class='ove']/div[@class='ove-right']/h1/a/@href")
if num > len(url_list):
raise Exception("疫情专区的新闻数量少于设定值")
# 如果新闻数量大于num,保留最新的num篇新闻
xpath_str = "//div[@class='eve'][position()<%d]//div[@class='ove']/div[@class='ove-right']/h1/a/@href" % (num+1, )
top_url_part_list = get_content_by_xpath(url=target_url1, xpath=xpath_str)
# 打印获取到的新闻报道URL
print(top_url_part_list)
# 创建一个新文件并将其内容清空,作为保存新闻内容的文件
with open(filename, "w", encoding='utf-8') as f:
f.truncate()
# 遍历获取的新闻URL并将新闻内容保存到本地文件
for url_part in top_url_part_list:
# 让爬虫程序随机休眠一段时间,模拟用户访问网站的行为
pause_time = random.random()
print("爬虫程序随机休眠%s秒" % (pause_time+1))
time.sleep(pause_time)
target_url2 = urljoin(target_url1, url_part)
# 从最新的新闻报道页面解析出报道文本并保存到本地文件
# 标题://h1
# 内容://div[@class='container']//div[@id='Content']/p/text()
title = get_content_by_xpath(url=target_url2, xpath="//h1/text()")
content = get_content_by_xpath(url=target_url2, xpath="//div[@id='Content']/p/text()")
content = [p+"\n" for p in content]
# 打印标题和内容
print("文章【{}】解析成功。".format(title[0]))
# 将新闻内容写入本地文件
with open(filename, "a+", encoding='utf-8') as f:
f.write(title[0])
f.write("\n")
f.writelines(content)
f.write("\n")
print("新闻内容全部提取完成!")
return filename
完成实验代码的粘贴后,按下 ESC
键切换到命令模式,并在英文模式下使用命令:wq
保存文件并退出编辑器。
2.get_news
模块代码解析
接下来将会对此模块的具体功能进行介绍。
注意:下方的代码介绍与上方粘贴的代码相同,主要是为了帮助学员理解代码功能,不需学员进行操作。
get_content_by_xpath
函数介绍
(1)定义请求头。
函数中首先使用下方命令,定义了请求头headers
:
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
headers
的定义格式为字典形式,发送请求时将其作为参数携带,可以将发送的请求模拟成浏览器的请求形式,以便获取到与浏览器中一致的内容。
(2)发送get请求并接收数据。
接下来使用requests
发送一个get类型的请求:
resp = requests.get(url=url, headers=headers)
此处包含了两个参数,url对应要请求的URL地址,headers对应上方定义的请求头。
此函数主要通过save_top_news
函数调用,每次调用时都会填写具体的URL地址,返回的结果是请求网页中的具体内容。
(3)通过XPath匹配内容。
对于获取到的网页内容,结合下面两行的代码将会实现具体的内容提取操作:
html_tree1 = HTML(resp.content)
content = html_tree1.xpath(xpath)
return content
其中第一行会将接收到的字符串数据转换成XPath解析对象,对于这一类型的对象,可以通过XPath表达式,提取节点中的数据,这个数据可以是URL地址或具体的文本信息。
第二行和第三行的内容,就是传入具体的XPath表达式,并将获取到的数据以文本的形式返回。
save_top_news
函数介绍
(1)获取疫情专区URL。
此函数第一部分的内容是从中国日报网的 首页 获取疫情专区的URL,此部分对应的代码如下:
resp1 = get_content_by_xpath(url=BASE_URL, xpath="//div[@class='tou-right']/a/@href")
target_url_part1 = resp1[0]
target_url1 = urljoin(BASE_URL, target_url_part1)
第一行调用了上方说明的get_content_by_xpath
方法,传入了人民日报首页作为URL地址,同时指定了XPath表达式:
//div[@class='tou-right']/a/@href
此表达式的具体功能是: 从页面中的任意位置找到包含类tou-right
的div,获取其中的a标签的href属性的值 。
通过此表达式,可以获取到页面中疫情专区的URL地址:
由于获取到的结果为列表形式,所以通过target_url_part1 = resp1[0]
提起到结果中的第一条数据,此时获取到的结果为字符串形式。
注:为了避免疫情过后实验结果发生变化,实际的项目代码中在这里进行了一步条件判断。如果获取到的结果发生变化,说明页面的展示专题做了调整,或是进行了重构,此时会将返回结果替换为疫情专区的固定访问地址,保证接下来的实验可以顺利进行。
通过urljoin
方法,将其与BASE_URL
进行拼接,最终组合出页面的完整地址:
https://cn.chinadaily.com.cn/a/202001/31/WS5e33aa27a3107bb6b579c52f.html
访问此地址,可以进入到疫情专区的页面,说明疫情专区URL的获取功能已经实现。
(2)获取最新的新闻报道URL。
中间一部分的代码主要是用于获取最新的新闻报道URL(默认指定获取最新的10篇新闻)。
首先会进行一步判断,确认页面中的专区新闻数量大于设定值:
url_list = get_content_by_xpath(url=target_url1, xpath="//div[@class='eve']//div[@class='ove']/div[@class='ove-right']/h1/a/@href")
if num > len(url_list):
raise Exception("疫情专区的新闻数量少于设定值")
如果新闻数量小于设定值,将会抛出异常:疫情专区的新闻数量少于设定值。
注:实验中的默认设置的获取新闻数量为10篇,当前的新闻数量远高于判断数量,但仍然保留此部分代码,是为了保证手动替换num的值后,不会因为新闻数量小于导致程序抛出异常错误。
如果新闻数量满足需求,接下来会保留最新的num篇新闻:
xpath_str = "//div[@class='eve'][position()<%d]//div[@class='ove']/div[@class='ove-right']/h1/a/@href" % (num+1, )
top_url_part_list = get_content_by_xpath(url=target_url1, xpath=xpath_str)
这一部分的代码与获取疫情专区URL的步骤相同,首先拼接XPath表达式,然后调用get_content_by_xpath
函数,获取满足条件的URL地址列表,获取到的结果存储在列表top_url_part_list
中。
(3)保存新闻内容到本地文件。
函数的最后部分是保存新闻内容到本地文件中,第一步会先创建一个新文件并将其内容清空,作为保存新闻内容的文件:
with open(filename, "w", encoding='utf-8') as f:
f.truncate()
清空内容的主要目的是避免列表中已经包含了同名文件,影响最终的实验效果。
接下来通过for循环,对提取到的URL地址列表进行遍历,遍历代码中首先有一段随机睡眠1~2秒的代码块:
pause_time = random.random()
print("爬虫程序随机休眠%s秒" % (pause_time+1))
time.sleep(pause_time)
设置随机睡眠时间的主要目的是模拟用户访问网站的行为,如果没有这一部分的代码。一方面过于频繁的请求,会给被请求方的服务器造成巨大的负载压力,严重时甚至会导致网站瘫痪;另一方面过于频繁的请求可能会让网站将请求识别为机器人,进而进行请求限制,影响接下来数据的获取。
下一行代码再次使用了urljoin
方法,目的同样是拼接出新闻页面的具体URL地址:
target_url2 = urljoin(target_url1, url_part)
接下来一部分两次调用get_content_by_xpath
函数,通过XPath表达式分别用于获取文章的标题和正文内容:
title = get_content_by_xpath(url=target_url2, xpath="//h1/text()")
content = get_content_by_xpath(url=target_url2, xpath="//div[@id='Content']/p/text()")
content = [p+"\n" for p in content]
# 打印标题和内容
print("文章【{}】解析成功。".format(title[0]))
获取到的多行正文会以列表形式展示,通过第三行的命令,会在每一行正文的尾部插入换行符\n
。完成以上操作后,会在控制台上打印文章解析成功的提示信息。
接下来这一部分的代码,会将新闻的内容写入本地文件:
# 将新闻内容写入本地文件
with open(filename, "a+", encoding='utf-8') as f:
f.write(title[0])
f.write("\n")
f.writelines(content)
f.write("\n")
写入文件的过程中,会再次用换行符\n
将新闻的标题和正文分隔开。
等到完成所有新闻内容的获取和保存后,执行以下两行代码:
print("新闻内容全部提取完成!")
return filename
第一行在控制台上打印提示信息,第二行会返回生成的文件名,方便下一步定位文本文件,并通过文件的内容生成词云图。
稍后执行提取新闻内容这一部分的代码时,将会在控制台展示类似下方的提示信息:
至此新闻数据的获取及保存的功能已经实现,下面将实现结合新闻数据,生成词云图的功能。
学员评价