疫情数据分析平台工作报告【1】数据采集

数据采集与获取

编辑:
先贴一下整理的相关fastcode:

1. BeautifulSoup的find方法
# for example

soup.find('a') # 根据标签名查找

soup.find(id='link1') # 根据属性查找

soup.find(attrs={'id':'link1'}) # 根据属性查找

soup.find(test='aaa') # 根据标签文本内容查找
2. Tag对象
find方法返回的是Tag对象,有如下属性

Tag对象对应于原始文档中的html标签

name:标签名称

attrs:标签属性的键和值

text:标签的字符串文本
3. 正则表达式
. \d

+*?

()

[]

\

r原串

import re

rs = re.findall('\d','123')
rs = re.findall('\d*','456')
rs = re.findall('\d+','789')
rs = re.findall('a+','aaabcd')

print(rs)
import re

# 分组的使用
rs = re.findall('\d{1,2}','chuan13zhi2')
rs = re.findall('aaa(\d+)b','aaa91b')
print(rs)

# 一般的正则表达式匹配一个\需要四个\
rs = re.findall('a\\\\bc','a\\bc')
print(rs)
print('a\\bc')

# 使用r原串
rs = re.findall(r'a\\rbc','a\\rbc')
print(rs)
4. json字符串互转python数据
import json

json_str = '''[{"a":"thia is a",
"b":[1,2,3]},{"a":"thia is a",
"b":[1,2,3]}]'''

rs = json.loads(json_str)
print(rs)
print(type(rs))  # <class 'list'>
print(type(rs[0]))  # <class 'dict'>
print(type(json_str))  # <class 'str'>
import json

json_str = '''[
  {
    "a": "this is a",
    "b": [1, 2,"熊猫"]
  },
  {
    "c": "thia is c",
    "d": [1, 2, 3]
  }
]'''

rs = json.loads(json_str)
json_str = json.dumps(rs,ensure_ascii=False)
print(json_str)
5. json格式文件互转python数据
# json格式文件转python数据
with open('data/test.json') as fp:
    python_list = json.load(fp)
    print(python_list)
    print(type(python_list))  # <class 'list'>
    print(type(python_list[0]))  # <class 'dict'>
    print(type(fp))  # <class '_io.TextIOWrapper'>
with open("data/test1.json",'w') as fp:
    json.dump(rs,fp,ensure_ascii=False)

整理疫情获取的渠道如下,这里仅仅列出几个常见的:

腾讯新闻https://news.qq.com/zt2020/page/feiyan.htm#%2F=
中国卫健委http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml
新浪新闻https://news.sina.cn/zt_d/yiqing0121
网易新闻https://wp.m.163.com/163/page/news/virus_report/index.html
TRT https://www.trt.net.tr/chinese/covid19
Tableau https://www.tableau.com/zh-cn/covid-19-coronavirus-data-resources/global-tracker
Outbreak https://www.outbreak.my/zh/world

新华网http://my‐h5news.app.xinhuanet.com/h5activity/yiqingchaxun/index.html
凤凰网https://news.ifeng.com/c/special/7uLj4F83Cqm
新浪网https://news.sina.cn/zt_d/yiqing0121
WHOhttps://covid19.who.int/
Tableau: https://www.tableau.com/covid‐19‐coronavirus‐data‐resources
Johns Hopkins:https://coronavirus.jhu.edu/map.html
Worldometers: https://www.worldometers.info/coronavirus/
CDC: https://www.cdc.gov/covid‐data‐tracker/#cases
各省份的卫健委、疫情防控部门的主页

我们着重看一下,在爬取境外网站的世界疫情数据时会遇到的一些反爬问题。

验证headers中的User-Agent字段
我们用的是Selenium,头字段肯定是有的,所以这点不攻自破。

限制用户必须登录
如果未登录或权限不够,则论坛直接禁止访问。

笔者从各种渠道获得了数十个不同的有权限的账号,全部正常登录一遍,获取cookie,组一个cookies池。在程序的最前面先随机选一个cookie加载。

同一账号/统一IP短时间内连续访问则返回广告页面

使用多个有权限的账号建cookie池;使用多个原生IP建cookie池;
给爬取循环添加随机sleep;

Cookies
论坛在校验cookie上有特殊的机制。除了两个用于验证用户名和id的memberID、userPasshash以外,还有一个用于验证账号合法性的随机生成的igneous字段。
多个账号cookie建池;每次访问之后清cookie;

AJAX
页面刷新结合了Ajax和传统模式。

就是为了克服Ajax使用的Selenium。这种类似无头浏览器的自动化工具可以很好处理Ajax动态拉取问题。

验证码
为了防止同Ip过多访问被封禁,我们使用了SoftEther来获取大量的原生Ip。如果走境外访问,一定几率会出Google平台的验证码ReCaptcha。

我们用国外的接码平台来搞。不同于一些小作坊式的数字/字幕验证码,谷歌的ReCaptcha还是不好搞。一开始想的是在本地部署一个深度学习模型,但是ReCaptcha的类型太多了,还是转向专业的接码平台。就是有点贵。

我们还是重点看各种反爬应对手段相关的代码。

验证headers中的User-Agent字段
我们用的是Selenium,看一下相关的代码。

options = webdriver.ChromeOptions()
prefs = {"profile.managed_default_content_settings.images": 2}
options.add_experimental_option("prefs", prefs)


driver_title = webdriver.Chrome(options=options, executable_path=chrome_driver)
driver_user = webdriver.Chrome(options=options, executable_path=chrome_driver)
driver_content = webdriver.Chrome(options=options, executable_path=chrome_driver)

限制用户必须登录
如果未登录或权限不够,则论坛直接禁止访问。

看一下用于模拟登陆,获取cookie的代码。

try:
    driver.get("https://bbs.nga.cn/thread.php?fid=-7")

    time.sleep(40)

    with open('cookies.txt', 'w') as cookiefile:
        # 将cookies保存为json格式
        cookiefile.write(json.dumps(driver.get_cookies()))

看一看cookie池存储部分的代码。

REDIS_HOST = 'localhost'
REDIS_PASSWORD = None

class RedisClient(object):
    def __init__(self,type,website,host=REDIS_HOST,port=REDIS_PORT,password=REDIS_PASSWORD):
        self.db = redis.StrictRedis(host=host,port=port,password=password,decode_responses=True)
        self.type = type 
        self.website = website  
    def name(self):
        return "{type}:{website}".format(type=self.type,website=self.website)
    def set(self,usename,value):
        return self.db.hset(self.name(),usename,value)
    def get(self,usename):
        return self.db.hget(self.name(),usename)
    def delete(self,usename):
        return self.db.hdel(self.name(),usename)
    def count(self):
        return self.db.hlen(self.name())
    def random(self):
        return random.choice(self.db.hvals(self.name()))
    def usernames(self):
        return self.db.hkeys(self.name())
    def all(self):
        return self.db.hgetall(self.name())

看一下生成部分。

def __init__(self,username,password,browser):
    self.url = ''
    self.browser = browser
    self.wait = WebDriverWait(browser,10)
    self.username = username
    self.password = password

def open(self):
    self.browser.get(self.url)
    self.wait.until(EC.presence_of_element_located((By.ID,'dologin'))).click()
    self.browser.switch_to.frame('loginIframe')
    self.wait.until(EC.presence_of_element_located((By.ID,'switcher_plogin'))).click()
    self.wait.until(EC.presence_of_element_located((By.ID,'u'))).send_keys(self.username)
    self.wait.until(EC.presence_of_element_located((By.ID,'p'))).send_keys(self.password)
    time.sleep(2)
    self.wait.until(EC.presence_of_element_located((By.ID,"login_button"))).click()

def password_error(self):
    try:
        # 当密码错误时会弹出一个提示,我们只需捕获这个错误提示就知道是否输入错误
        return bool(self.wait.until(EC.presence_of_element_located((By.ID,'err_m'))))
    except ex.TimeoutException:
        return False
def get_cookies(self):
    return self.browser.get_cookies()

同一账号/统一IP短时间内连续访问则返回广告页面

上面已经看了cookie池的相关代码,下面我们看一下IP池相关的部分。

IP池是通用的,我们这里直接采用开源的GitHub上面的项目proxy_list。

#持久化
PERSISTENCE = {
    'type': 'redis',
    'url': 'redis://127.0.0.1:6379/1'
}
 协程并发数
#爬取下来的代理测试可用性时使用,减少网络 io 的等待时间
COROUTINE_NUM = 50
 保存多少条代理
 默认200,如果存储了200条代理并不删除代理就不会再爬取新代理
PROXY_STORE_NUM = 300

 如果保存的代理条数已到阀值,爬取进程睡眠秒数
 默认60秒,存储满200条后爬虫进程睡眠60秒,醒来后如果还是满额继续睡眠
PROXY_FULL_SLEEP_SEC = 60
#已保存的代理每隔多少秒检测一遍可用性
PROXY_STORE_CHECK_SEC = 1200
#web api
 指定接口 IP 和端口
WEB_API_IP = '127.0.0.1'
WEB_API_PORT = '8111'

验证码
为了防止同Ip过多访问被封禁,我们使用了SoftEther来获取大量的原生Ip。如果走境外访问,一定几率会出Google平台的验证码ReCaptcha。

看一下用国外的接码平台的代码。接码平台除了要花钱以外没有任何缺点。这个价格其实也可以接受,毕竟我们个人爬,也用不了多少。
在这里插入图片描述

构造发给打码平台的请求。

import requests
response = requests.get(url)
print(response.json())
https://2captcha.com/in.php?key=c0ae5935d807c28f285e5cb16c676a48&method=userrecaptcha&googlekey=6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&pageurl=https://www.google.com/recaptcha/api2/demo&json=1

当页面出现ReCaptcha验证码的时候,这个验证码是放在一个外部frame里面的。我们可以找到验证码的唯一id。我们直接把这个id提交给打码平台即可。大概10-30秒钟就会返回结果。然后平台会返回接口id。
通过接口id获取最后的结果,是一个加密的token。
把这个token赋值给验证码对应的表单提交即可。

document.getElementById("g-recaptcha-response").innerHTML="TOKEN_FROM_2CAPTCHA";

环境:python 3.8

例如,爬取来源于腾讯的疫情数据。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

import requests
import json
import pprint
import pandas as pd

发送请求
url = ‘https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5&_=1638361138568’
response = requests.get(url, verify=False)
复制
获取数据
json_data = response.json()[‘data’]
复制
解析数据
json_data = json.loads(json_data)
china_data = json_data[‘areaTree’][0][‘children’] # 列表
data_set = []
for i in china_data:
data_dict = {}
data_dict[‘province’] = i[‘name’]
data_dict[‘nowConfirm’] = i[‘total’][‘nowConfirm’]
data_dict[‘dead’] = i[‘total’][‘dead’]
data_dict[‘heal’] = i[‘total’][‘heal’]
data_dict[‘healRate’] = i[‘total’][‘healRate’]
data_set.append(data_dict)
复制
保存数据
df = pd.DataFrame(data_set)
df.to_csv(‘data.csv’)

import  requests
import json
import csv
url='https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5&callback=jQuery351007009437517570039_1629632572593&_=1629632572594'
head={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'
}
response = requests.get(url,headers=head).text
print(response)
dict1 =json.loads(response[42:132060])#这个切片位置是会变化的,需要我们去分析每天变化的位置,然后再去切片
d = json.loads(dict1['data'])#json是转化抓取来的数据格式,json.load是将字符串转化为字典的格式
print(d)
all_dict={}
all_dict['统计时间']=d["lastUpdateTime"]
chinaTotal = d['chinaTotal']
all_dict['累计确诊病例'] = chinaTotal['confirm']
all_dict['现有确诊'] = chinaTotal['nowConfirm']
all_dict['治愈病例'] = chinaTotal['heal']
all_dict['死亡病例'] = chinaTotal['dead']
all_dict['本土昨日新增'] = chinaTotal['suspect']
all_dict['境外输入'] = chinaTotal['importedCase']
all_dict['无症状感染者'] = chinaTotal['noInfect']
print(all_dict)
print(chinaTotal)
with open('yqin.csv','w+',newline='')as f:
    f1= csv.writer(f)
    list1=[]
    f1.writerow(all_dict)
    for i in all_dict:
        list1.append(all_dict[i])
    f1.writerow(list1)
# print(chinaTotal)
area = d['areaTree'][0]
# for i in area:
#     print(area[i])
children = area['children']
with open('yq1.csv','w+',newline = '')as f:
    list1= ['省份','现有病例','昨日新增','累计病例','死亡人数','治愈人数']
    f1 = csv.writer(f)
    f1.writerow(list1)
    for i , index,in enumerate(children):
        cc = children[i]
        dd = []
        dd.append(cc['name'])
        dd.append(cc['total']['nowConfirm'])
        dd.append(cc['today']['confirm'])
        dd.append(cc['total']['confirm'])
        dd.append(cc['total']['dead'])
        dd.append(cc['total']['heal'])
        f1.writerow(dd)
        print(dd)

![在这里插入图片描述](https://img-blog.csdnimg.cn/5061d115ecab4b8d86877dcd2c9905b2.png)

import json
import re
import requests
import datetime

today = datetime.date.today().strftime('%Y%m%d')

def crawl_dxy_data():
    """爬取丁香园实时统计数据,保存在data目录下,以当前日期作为文件名,文件格式为json格式
    """
    response = requests.get('https://ncov.dxy.cn/ncovh5/view/pneumonia')  # 发送get请求
    print(response.status_code)  # 打印状态码

    try:
        url_text = response.content.decode()  # 获取响应的html页面
        url_content = re.search(r'window.getAreaStat = (.*?)}]}catch',   # re.search()用于扫描字符串以查找正则表达式模式产生匹配项的第一个位置,然后返回相应的match对象
                                url_text, re.S)                         # 在字符串a中,包含换行符\n,这种情况下:如果不适用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始匹配
        texts = url_content.group()  # 获取匹配正则表达式的整体结果
        content = texts.replace('window.getAreaStat = ', '').replace('}catch', '')  # 去除多余字符
        json_data = json.loads(content)
        with open('data/' + today + '.json', 'w', encoding='UTF-8') as f:
            json.dump(json_data, f, ensure_ascii=False)
    except:
        print('<Response [%s]>' % response.status_code)

crawl_dxy_data()



![在这里插入图片描述](https://img-blog.csdnimg.cn/5bbea9059c0a41dcba58091ffb2f8c69.png)






# 获取当日国内疫情数据

def get_data(request):
    response_data = json.loads(request.text)
    all_data = response_data['data']  # 返回的数据
    last_update_time = all_data['lastUpdateTime']  # 上次更新时间

    # 总体数据
    china_total = all_data['chinaTotal']  # 总计
    total_confirm = china_total['confirm']  # 累计确诊
    total_heal = china_total['heal']  # 累计治愈
    total_dead = china_total['dead']  # 累计死亡
    now_confirm = china_total['nowConfirm']  # 现存确诊(=累计确证-累计治愈-累计死亡)
    suspect = china_total['suspect']  # 疑似
    now_severe = china_total['nowSevere']  # 现存重症
    imported_case = china_total['importedCase']  # 境外输入
    noInfect = china_total['noInfect']  # 无症状感染
    local_confirm = china_total['localConfirm']  # 本土确诊

    # 新增数据
    china_add = all_data['chinaAdd']  # 新增
    add_confirm = china_add['confirm']  # 新增累计确诊?
    add_heal = china_add['heal']  # 新增治愈
    add_dead = china_add['dead']  # 新增死亡
    add_now_confirm = china_add['nowConfirm']  # 新增现存确诊
    add_suspect = china_add['suspect']  # 新增疑似
    add_now_sever = china_add['nowSevere']  # 新增现存重症
    add_imported_case = china_add['importedCase']  # 新增境外输入
    add_no_infect = china_add['noInfect']  # 新增无症状

    # print(china_add)

    area_Tree = json.loads(response_data['data'])['areaTree']  # 各地区数据
    for each_province in area_Tree[0]['children']:
        # print(each_province)
        province_name = each_province['name']  # 省名
        province_today_confirm = each_province['today']['confirm']  # 省内今日确诊总数
        province_total_confirm = each_province['total']['nowConfirm']  # 省内现存确诊总数
        province_total_confirmed = each_province['total']['confirm']  # 省内确诊总数
        province_total_dead = each_province['total']['dead']  # 省内死亡总数
        province_total_heal = each_province['total']['heal']  # 省内治愈总数
        province_total_localConfirm = each_province['total']['provinceLocalConfirm']  # 省内本土确证数

        for each_city in each_province['children']:
            city_name = each_city['name']  # 市名
            city_today_confirm = each_city['today']['confirm']  # 市内今日确诊数
            city_total_confirm = each_city['total']['nowConfirm']  # 市内现存确诊数
            city_total_confirmed = each_city['total']['confirm']  # 市内确诊总数
            city_total_dead = each_city['total']['dead']  # 市内死亡病例数
            city_total_heal = each_city['total']['heal']  # 市内治愈数
            city_grade = ''  # 市风险等级
            if 'grade' in each_city['total']:
                city_grade = each_city['total']['grade']

            print("省份:" + province_name +
                  " 地区:" + city_name +
                  " 今日确诊新增确诊:" + str(city_today_confirm) +
                  " 现存确诊:" + str(city_total_confirm) +
                  " 风险等级:" + city_grade +
                  " 累计确诊:" + str(city_total_confirmed) +
                  " 治愈:" + str(city_total_heal) +
                  " 死亡:" + str(city_total_dead))


if __name__ == '__main__':
    # 接口
    api = 'https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=statisGradeCityDetail,' \
          'diseaseh5Shelf '
    head = {'User-Agent': 'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, ' \
                          'like Gecko) Chrome/18.0.1025.166  Safari/535.19 '}
    req = requests.get(api, headers=head)
    print(req.text)
    get_data(req)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值