较真查证平台信息抓取

近期社会广泛关注武汉肺炎疫情,各类网传、听说类谣言信息误导公众,引发社会恐慌。而腾讯较真查证平台就是将网络上的谣言进行收集并解答,而今天我们要做的就是抓取该平台上的谣言信息。

分析请求

我们使用借助浏览器的开发者工具来分析请求。首先使用Chrome浏览器打开较真查询平台网站https://vp.fact.qq.com/home ,然后在页面中点击鼠标右键,从弹出的快捷菜单中选择检查选项,此时便可以弹出开发这工具。

chrome开发者工具.png

滚轮下拉刷新数据,可以看到下面的数据请求: 请求数据.png

排除其中type是jpeg png这样的图片文件,剩下一条就是我们需要的数据,选择第一条然后单击在右边的选择Preview可以看到就是我们需要的数据:
后台数据.png

针对于每一条请求都可以在开发者工具中查看他们的Headers与Response。而针对于较真查证平台第一条请求就是我们需要的,现在我们来分析这个请求的headers: 请求的Headers.png

其中可以看到该请求是一个GET请求,url是https://vp.fact.qq.com/loadmore 而参数有四个artnum page _ 以及callback。这里我们不断下拉来得到新的请求数据,来观察这四个参数的值。得到一下结论:

  • artnum 都是 0
  • page 为页面值,从0开始
  • _ 其值是请求是的时间戳
  • callback 回调函数,可以是任意字符串

分析响应

我们通过开发者工具来查看这条请求的相应:
响应.png

可以看到这是一个像json的字符串,不过由jsonp1()包括,只要我们删除了这几个字符就可以当作json格式了。

编写代码

这里我们使用python的requests库来编写程序,使用sqlite3来保存抓取到的数据,规划步骤如下:

  1. 构建url并使用requests.get()来获取数据
  2. 截取得到的数据,使用json.loads()函数来得到字典
  3. 将数据写入sqlite3数据库中

构建get请求并获取数据

In [4]:
import requests
import time
import json
# 请求网址
url = "https://vp.fact.qq.com/loadmore"

# 请求参数
params = {
    'artnum':0,
    'page':0,
    '_':1581846620972,
    'callback':'jsonp1',
}

# 构建请求头
headers  = {
    'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Mobile Safari/537.36',
    'referer': 'https://vp.fact.qq.com/home'
}

# 获得数据
def getMessage(page):
    params['_'] = int(round(time.time()*1000))
    params['page'] = page
    req = requests.get(url,headers=headers,params=params).text
    return json.loads(req[7:-1])['content']

# 测试下可以程序可以看到没有问题
print(getMessage(0)[0])
{'title': '武汉病毒所女研究生黄燕玲是新冠肺炎零号病人', 'author': '腾讯新闻旗下专业事实查证平台', 'authordesc': '腾讯新闻旗下专业事实查证平台', 'id': 'aa9851bacbb24a4e8b3cee11c02c26bf', 'date': '2020-02-16', 'markstyle': 'fake', 'result': '假', 'explain': '谣言', 'abstract': '黄燕玲2015年毕业后就离开了武汉病毒研究所...', 'tag': ['新型冠状病毒肺炎', '零号病人'], 'type': 1, 'videourl': '', 'cover': '//jiaozhen-70111.picnjc.qpic.cn/e6n1NXgYZyWWQDDhKzc6uj?imageView2/2/w/150/h/90', 'coverrect': '//jiaozhen-70111.picnjc.qpic.cn/e6n1NXgYZyWWQDDhKzc6uj', 'coversqual': '//jiaozhen-70111.picnjc.qpic.cn/e6n1NXgYZyWWQDDhKzc6uj?imageView2/2/w/300/h/300', 'section': '', 'iscolled': False, 'arttype': 'normal'}

将数据写入到数据库中

通过getMessage()函数得到的是一个list对象,其中每一条都是一个字典对象存储了谣言相关的信息,包括:title、author、authordesc、id等信息。我们创建在本地创建一个sqlite3数据库并且添加yiqing数据表:

PS E:\credher\gitee\spider\较真谣言> sqlite3 jiaozhen.db
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
sqlite> create table yiqing(abstract text,arttype text,author text,authordesc text
,cover text,coverrect text,coversqual text,date text,explain text,id text,iscolled
 text,markstyle text,result text,section text,tag text,title text,type text,videou
rl text);
sqlite> .table
yiqing
In [10]:
import sqlite3

# 循环获取数据并写入数据库
def writeToSql(conn):
    page = 0
    while True:
        results = getMessage(page)
        if len(results) == 0:
            break
        for result in results:
            tags = ""
            for tag in result['tag']:
                tags = tags + tag
            conn.execute('insert into yiqing values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',(result['abstract'],result['arttype'],result['author'],result['authordesc'],result['cover'],result['coverrect'],result['coversqual'],result['date'],result['explain'],result['id'],result['iscolled'],result['markstyle'],result['result'],result['section'],tags,result['title'],result['type'],result['videourl']))
        conn.commit()
        print(page)
        page = page + 1
    conn.close()
# 运行就好了
# conn = sqlite3.connect('jiaozhen.db')
# writeToSql()

总结

这样所有关于疫情的辟谣数据就完成了抓取:

sqlite> select count() from yiqing;
320

可看到截至到2月16日一共有320条数据。如果此时你要更高的需求想要抓取较真平台上更多的数据,这是我们就需要使用他的搜索工具了,接下来来完成这部分的数据抓取。

分析搜索请求

与上面的分析一样,以搜索癌症为例,在搜索后在开发者工具的Network中找到对应的请求数据:

搜索请求数据.png

可以看到请求的URL是https://vp.fact.qq.com/searchresult 而参数如下:

  • title 搜索的关键词
  • num 搜索数据页,可以在相应中看到每页有20个数据,num的最大值就是 total/20
  • _ 时间戳
  • callback 回调函数,可以是任意字符串

分析搜索响应

搜索响应.png

其结果依然是一个类似的json字符串,不过依然需要切片删除jsop1(),这里面的total决定了你请求中的num的最大值,其数据保存在content中,一共有20条。

有关谣言的关键词

由于搜索需要相应的关键词,可以自行构建,这里我们使用较真平台中大家都在查这些中出现的关键词作为搜索依据,依然根据上面的分析过程可以知道这部分关键词从https://vp.fact.qq.com/searchPage?ADTAG=&_=1581854494858&callback=jsonp1 中获取,这个请求的响应是一段被jsop1()包括的html字符串:

jsonp1("\u003Cdiv id=\"search\">\n    \u003Cdiv class=\"top\">\n        \u003Cimg src=\"//mat1.gtimg.com/www/coral/jiaozhen/imgs/toplogo_small.png\" class=\"top_logo\" id=\"search_top_logo\">\n        \u003Cdiv class=\"top_search_div\">\u003Cform id=\"search_form\" action=\"\">\u003Cinput class=\"top_search\" id=\"top_search\" autocomplete=\"off\" type=\"search\" placeholder=\" 输入关键词查一查 \" autofocus=\"autofocus\"/>\u003C/form>\u003C/div>\n        \u003Cimg src=\"//mat1.gtimg.com/www/coral/jiaozhen/imgs/close.png\" id=\"top_search_cancle\">\n        \u003Cspan class=\"top_cancel\" id=\"top_cancel\">取消\u003C/span>\n        \u003Cbutton id=\"btn\" style=\"display: none\">\u003C/button>\n    \u003C/div>\u003Cdiv class=\"content\">\n\n    \u003Cdiv class=\"content_title\" id=\"content_title\">大家都在查这些:\u003C/div>\n    \u003Cdiv class=\"content_list\" id=\"content_list\">\n        \u003Cul>\n                \u003Cli class=\"hot_search_li\">\n                    \u003Cdiv class=\"content_text\">武汉病毒所女研究生黄燕玲是新冠肺炎零号病人\u003C/div>\n                \u003C/li>\n                \u003Cli class=\"hot_search_li\">\n                    \u003Cdiv class=\"content_text\">2月17日起至疫情防控结束全国高速所有车免费通行\u003C/div>\n

不过其中的小于号使用的是\uxxxx这样的字符串,如果想要转换为正常的字符串需要对其解码。然后就是一串HTML字符串了可以使用任何方式来进行数据提取。

编写代码

现在我们来编写代码,其具体步骤如下:

  1. 提取谣言关键字
  2. 构建搜索请求并处理响应数据
  3. 将得到的数据存入sqlite3数据库中

提取谣言关键字

In [15]:
from bs4 import BeautifulSoup
import re

def getKeywords():
    content = requests.get("https://vp.fact.qq.com/searchPage?ADTAG=&_=1581854494858&callback=jsonp1").text
    # 将\uxxxx解码为相应的字符
    content =  re.sub(r'(\\u[\s\S]{4})',lambda x:x.group(1).encode("utf-8").decode("unicode-escape"),content)
    # 使用bs4来获取html中数据
    soup = BeautifulSoup(content)
    results = soup.find_all(class_='\\"content_text\\"')
    keywords = []
    for result in results:
        keywords.append(result.string)
    return keywords

构建搜索请求并处理响应数据

In [34]:
searchURL = "https://vp.fact.qq.com/searchresult"
searchParams = {
    'title':'癌症',
    'num':0,
    '_':1581854497987,
    'callback':'jsonp1',
}
def getSearchMessage(keyword):
    searchParams['title'] = keyword
    searchParams['num'] = 0
    req = requests.get(searchURL,params=searchParams,headers=headers).text
    total = json.loads(req[7:-1])['total']
    content =  json.loads(req[7:-1])['content']
    results = []
    for result in content:
        results.append(result['_source'])
    for num in range(1,int(total/20)+1):
        searchParams['num'] = num
        req = requests.get(searchURL,params=searchParams,headers=headers).text
        content =  json.loads(req[7:-1])['content']
        for result in content:
            results.append(result['_source'])
    return results

    

写入数据

在jiaozhen.db中创建表yaoyan:

create table yaoyan(id text primary key not null,title text,result text,source text,oriurl text,abstract text,cover text,updatedAt text,date text,author text,authordesc text);

In [37]:
def writeSearchToSql(conn):
    # 获取关键字列表,你也可以自行构建keywords
    keywords = getKeywords()
    for keyword in keywords:
        print(keyword)
        results = getSearchMessage(keyword)
        for result in results:
            try:
                if 'oriurl' not in result.keys():
                    result['oriurl'] = ''
                if 'cover' not in result.keys():
                    result['cover'] = ''
                if 'source' not in result.keys():
                    result['source'] = ''
                conn.execute('insert into yaoyan values(?,?,?,?,?,?,?,?,?,?,?)',(result['id'],result['title'],result['result'],result['source'],result['oriurl'],result['abstract'],result['cover'],result['updatedAt'],result['date'],result['author'],result['authordesc']))
                conn.commit()
            except sqlite3.IntegrityError:
                print('Error %s已经存在于数据库中'%result['id'])
    conn.close()
# 取消注释运行就可以了
# conn = sqlite3.connect('jiaozhen.db')
# writeSearchToSql(conn)
# print('ok')

总结

这样我们就得到了较真中存在的谣言相关数据,其分别保存在yiqin和yaoyan两个表中,我们可以在sqlite3中查询结果:

sqlite> select count() from yiqing;
320
sqlite> select count() from yaoyan;
4898

其中搜索得到的数据差不多有近5000条,这并不是全部,如果想要获取更多的数据就需要自己构建writeSearchToSql()函数中的keywords参数就可以了。