​原文在此,欢迎关注~

https://mp.weixin.qq.com/s/8pWtSFrgkyyBiWL_hHwmQw

Hello大家好,我是你们的机房老哥
抗“疫”期间,我也和大家一样,
呆呆地呆在家里……
我们知道,人一旦闲着无聊,就会有购买欲。
但是,不上学的我们,甚至连生活费都没有。

对于这种情况,老哥想说,何不趁此机会在家提升技术,以后更好的为国家做贡献呢?打开手机,其实也无心购物,一心关注武汉疫情。最好的办法自然就是看新闻啦。但是,懒惰的老哥并不想一条一条点进去看,而是想通过网络爬虫一次性获取所有的新闻的标题、关键词、简介、内容等信息,并下载到txt中随心所欲的查看。

▲先预览一下抓取到的新闻
当然啦,学会此术,爬个网文小说学习资料。。(咳咳)

接下来,就是见证奇迹的时刻。老哥带你用33行代码,一次性爬取央视新闻(http://CCTV.COM)上的所有国内新闻。点击关注,后台回复“新闻”,即可获得源代码哦!

央视网是一个实时加载的动态网站,会实时加载最近5天的新闻,并随时更新。相比于静态网站有一定的难度,但是这并难不倒老哥。那么,接下来就是:

新闻的正确打开方式——老哥爬虫进阶之JSON破解
你即将学到的是

fake_useragent库创造假请求头
requests库的螺旋升级版requests_html库的使用
json动态网站的破解
Step 1 查看源代码
CCTV央视网url:http://news.cctv.com/china/打开网址,按F12进入开发者模式。点击“网络”,进入网络监视器。点击“XHR”按钮,按F5刷新网页,可以看到,我们已经抓到了一个XHR对象了。

▲抓取XHR对象
XHR,全称为XML Http Request,用于与服务器交互数据,是ajax功能实现所依赖的对象。XML对象提供了对 HTTP 协议的完全的访问,包括做出POST和 HEAD请求以及普通的GET请求的能力。XHR可以同步或异步地返回 Web 服务器的响应,并且能够以文本或者一个 DOM 文档的形式返回内容。

Step 2 查看消息头和参数
现在已经抓取到了一个XHR对象,观察消息头,可以看到新闻的请求方法为get,网址为:http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_1.jsonp?cb=t&cb=china。我们之前说过,网址是动态加载的,所以会随着用户的操作不断加载出新的内容。所以,我们向下拉滑竿,点击“加载更多”按钮,可以抓取到更多的XML对象。

▲更多XHR对象
这里可以用Selenium来实现WEB自动化操作,模拟人拉滑竿的动作,但是这不是今天的重点,以后再给大家讲解。我们观察一下抓取到的第二个网址,该网址为:http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_2.jsonp?cb=t&cb=china。通过对比第一个网址和第二个网址,可以发现他们的的区别在于“china_”后面的参数,1代表第一页,2代表第2页。这样我们就得分析出了网址结构。
通过不断点击“加载更多”按钮,我们共获得了7页网址。点击参数按钮,可以获取到JSON的参数,观察发现,每一页的参数都相同,我们右键复制这个参数。

Step 3 抓取网页

现在一般的网络爬取都采用的是requests库,首先用requests库获取相应,然后用beautifulSoup库、re库、xpath去获取网页内容,复杂又繁琐。
最近,老哥网上冲浪,发现了一个了不得的宝贝。那就是:requests-html库。

requests-html库是requests库的作者发布的新库,短短几周就获得了上万Star,可以称得上是原版的螺旋升级版。github地址:https://github.com/kennethreitz/requests-html

▲作者对该库的介绍
这个库对现有的爬虫框架PyQuery、Requests、lxml、beautifulsoup4等库进行二次封装。现在我们只需要这一个库,就可以完成所有操作,简洁又强大!接下来让我们体验一下这个魔法般的库。注意该库只支持python3.6以上版本。首先安装requests-html库:

pip install requests-html
接下来,导入所需的库。其中,random库配合fake_useragent生成随机的假请求头,json库用于解析JS信息,requests_html库用于抓取网页数据。代码如下:

import random
import json
from requests_html import HTMLSession
from fake_useragent import UserAgent
我们刚才已经解析了网址结构,并得知新闻一共有7页,所以采取for循环来构造网址。接下来对我们的爬虫进行一定的伪装。先用fake_useragent库生成一个假的UserAgent,然后将刚才复制的参数变成字典格式,储存到“params”中,将两个参数都传入requests_html中,通过r=session.get(url)命令抓取网页信息。

def find_():
for i in range(7):
url = ‘http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_’+str(i+1)+’.jsonp?cb=t&cb=china’
headers = {‘User-Agent’:UserAgent().random}
params = {“cb”:[“t”,”china”]}
session = HTMLSession()
r = session.get(url, headers=headers, params=params)
这里我们打印一下r.html.text查看获取到的文本信息:

▲返回的r.html.text值
可以看到,我们已经成功抓取到网页的信息了。观察抓取到的信息,发现一个JSON结构被包在了“china()”里面,只要去掉这个壳,里面的JS信息就可以用json.loads工具进行解析。所以,采用replace方法去掉开头的“china(”和结尾的“)”,然后用json.loads解析。代码如下:

def find_():
for i in range(7):
url = ‘http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_’+str(i+1)+’.jsonp?cb=t&cb=china’
headers = {‘User-Agent’:UserAgent().random}
params = {“cb”:[“t”,”china”]}
session = HTMLSession()
r = session.get(url, headers=headers, params=params)
result = r.html.text
result = result.replace(‘china(‘, ”).replace(‘)’, ”)
result = json.loads(result)
进一步观察内部的JS信息,可以看到所有的新闻都被保存在“list”元素下,而每一条新闻都是list元素下的列表中的一个字典元素。我们提取list元素并打印,结果如下:

▲打印list返回值
可以看到,list中的每个字典都包含了新闻的id编号,title新闻标题,keywords新闻关键词,focus_date新闻发布日期,brief新闻简介,url新闻链接,image新闻图片等信息。因为list里面有很多新闻,所以我们用for循环来提取这些信息。代码如下:

def find_():
for i in range(7):
url = ‘http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_’+str(i+1)+’.jsonp?cb=t&cb=china’
headers = {‘User-Agent’:UserAgent().random}
params = {“cb”:[“t”,”china”]}
session = HTMLSession()
r = session.get(url, headers=headers, params=params)
result = r.html.text
result = result.replace(‘china(‘, ”).replace(‘)’, ”)
result = json.loads(result)
lst = result[‘data’].get(‘list’)
for content in lst:
keywords_ = content[‘keywords’]
title_ = content[‘title’]
url_ = content[‘url’]
brief_ = content[‘brief’]
focus_date_ = content[‘focus_date’]
获得了每一条新闻的URL之后,我们可以对这些URL进行进一步的爬虫,以获取每条新闻的具体内容。首先,我们打印所有的URL,并随即点进去查看新闻。

▲获取新闻URL

▲发现所有的新闻内容都包含在id="content_area"标签下
然后,我们再次使用session.get()命令提取URL。之前提到过requetsts_html库封装了XPATH和CSS Selector,所以我们可以使用CSS选择器来选取id=”content_area”下的所有P标签,并使用html.text方式获取P标签内的所有内容,即所有的新闻文本,代码如下:

def find_():
for i in range(7):
url = ‘http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_’+str(i+1)+’.jsonp?cb=t&cb=china’
headers = {‘User-Agent’:UserAgent().random}
params = {“cb”:[“t”,”china”]}
session = HTMLSession()
r = session.get(url, headers=headers, params=params)
result = r.html.text
result = result.replace(‘china(‘, ”).replace(‘)’, ”)
result = json.loads(result)
lst = result[‘data’].get(‘list’)
for content in lst:
keywords_ = content[‘keywords’]
title_ = content[‘title’]
url_ = content[‘url’]
brief_ = content[‘brief’]
focus_date_ = content[‘focus_date’]
r = session.get(url_)
content = r.html.find(‘#text_area p,#content_area p’)
for i in content:
content_ = i.text
这里涉及到一点CSS的知识,#代表ID标签,所以我们用#content_area就可以选择到id=”content_area”标签下的内容,因为我们想获取P标签,所以空格加P,空格代表P标签是#content_area标签下的子标签。运行代码,结果如下:

▲发现红框中的网址的新闻并没有爬下来
老哥满怀信心的运行代码,结果发现有部分网站的信息没有爬下来。所以我们进入这些网站,发现原来这部分网站的新闻内容是放在id=”text_area”下的。

▲这部分网址的ID内容发生了变化
我们返回去优化我们的代码,将id=”text_area”加入到CSS选择器中,代码如下:

content = r.html.find(‘#text_area p,#content_area p’)
这里面讲一下CSS选择器是可以同时选择两种内容的,两个表达式之间需要用“,”隔开。可以看到,我们使用了requetst_html库之后代码异常的简洁,只需要4行代码就可以爬下所有的新闻内容。

最后,我们用.format方法将新闻的简介、标题等信息和新闻的具体内容都写入一个txt文本当中。完整代码如下:

import random
import json
from requests_html import HTMLSession
from fake_useragent import UserAgent
def find_():
for i in range(7):
url = ‘http://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/china_’+str(i+1)+’.jsonp?cb=t&cb=china’
headers = {‘User-Agent’:UserAgent().random}
params = {“cb”:[“t”,”china”]}
session = HTMLSession()
r = session.get(url, headers=headers, params=params)
result = r.html.text
result = result.replace(‘china(‘, ”).replace(‘)’, ”)
result = json.loads(result)
lst = result[‘data’].get(‘list’)
out_txt = open(f’第{i+1}页新闻.txt’, ‘a’, encoding=’utf-8′)
tql = ‘{:10}\n{:10}\n{:10}\n{:10}\n{:10}’
print(f’正在打印第{i+1}页新闻…………….’)
for content in lst:
keywords_ = content[‘keywords’]
title_ = content[‘title’]
url_ = content[‘url’]
brief_ = content[‘brief’]
focus_date_ = content[‘focus_date’]
r = session.get(url_)
content = r.html.find(‘#text_area p,#content_area p’)
print(‘|’*100, file=out_txt)
print(tql.format(title_, focus_date_, keywords_, brief_, url_), file=out_txt)
for i in content:
content_ = i.text
out_txt.write(content_+’\n’)
out_txt.close()
find_()
这样,我们只用了33行代码,就获取到了所有的新闻内容。是不是很强大呢!

▲最终爬取到的新闻
怎么样,是不是很简单~
这样就可以一次看个够了!
最后,和老哥一起为武汉加油吧!_

最后推广一下我的公众号“机房老哥”,欢迎关注~