利德治疗仪查询 手把手教你完成一个数据科学小项目(2):数据提取、IP-雷课

查询 手把手教你完成一个数据科学小项目(2):数据提取、IP-雷课

背 景
之前在公众号发过的文章里已经讲过本系列相关的部分内容,当然本篇也将会全面涉及本项目从爬虫、数据提取与准备、数据异常发现与清洗、分析与可视化等细节,并将代码统一开源在GitHub:DesertsX/gulius-projects,希望可以帮助大家。
小编现在先在这里声明一下,最近《延禧攻略》比较火,所以所有的插件风格小编自作主张全换成清朝风格了!如引起不适,小编在这里诚恳的向您表示歉意!好了,正题开始!
前期准备
如果您在看本篇时会觉得有些突兀的话,可以先阅读微博上的《“中国年轻人正带领国家走向危机”,这锅背是不背?》一文(不是给微博打广告哦!),以对“手把手教你完成一个数据科学小项目”系列有个全局性的了解。亦或者,查看本公众号的上一篇文章《手把手教你完成一个数据科学小项目(1) 数据爬取》,(1)数据爬取里讲解了如何用爬虫爬取新浪财经《中国年轻人正带领国家走向危机》一文的评论数据,其中涉及的抓包过程是蛮通用的,大家如果想爬取其他网站,也会是类似的流程,当然介绍的比较粗浅,可自行深入扩展学习。
本文将在上回爬到的数据基础上,进一步地完成数据的提取,同时调用(再写个爬虫)某 IP 查询网站由评论里的 IP 查询到更多信息)。
同样的,所有操作都是用 Python 的 pandas 等数据科学库完成的。

数据提取
读取数据含义:数据提取是从数据源中提取数据的过程。
实际应用中,数据源较多采用的是关系数据库。从数据库中抽取数据一般有以下几种方式。折叠全量抽取
全量抽取类似于数据迁移或数据复制,它将数据源中的表或视图的数据原封不动的从数据库中抽取出来,并转换成自己的ETL工具可以识别的格式近战召唤师 。全量抽取比较简单。折叠增量抽取
增量抽取指抽取自上次抽取以来数据库中要抽取的表中新增、修改、删除的数据。在ETL使用过程中。增量抽取较全量抽取应用更广。如何捕获变化的数据是增量抽取的关键。对捕获方法一般有两点要求:准确性巴宝利官网,能够将业务系统中的变化数据准确地捕获到;性能,尽量减少对业务系统造成太大的压力,影响现有业务。
做法:
我们首先读取微博的前10行数据,每行是评论区一页包含的数据:
具体代码如下
import pandas as pd
df = pd.read_csv('Sina_Finance_Comments_1_20180811.csv',encoding='utf-8')
df.head(10)

上次在我们存储数据时,存了'page'、 'jsons'、 'cmntlist'、'replydict' 四列,其分别对应页数、每页全部数据(仅存这列也行)、从 jsons 里提取出来含每页20条的评论主体的数据、从 jsons 里提取出来的互相回复的评论数据,这部分后续暂没挖掘,感兴趣的朋友可以试着将所有节点绘制成网络图,虽然效果如何不确定。两篇旧文可供参考:Gephi绘制微博转发图谱:以别人做过的一个为例,可以看出配图也是专栏的logo:
这是引用的别人的:
374名10万+知乎大V(一):相互关注情况,均用 Gephi 实现
好了,咱们现在回归正题,现在我们将cmntlist 列的元素转换成列表格式(列表嵌套‘列表’,因为每个元素本身也是‘列表’),并打印元素格式发现看起来是‘列表’,其实字符串格式,需要用eval()函数将列表样、字典样的字符串转换成列表或字典:
具体代码如下
cmntlist = df.cmntlist.values.tolist()
print(len(cmntlist))
print(type(cmntlist[0])) # str 字符串格式
#print(cmntlist[0]) # 篇幅太长占地方
print(cmntlist[0][:200]) # 截取前200字符展示
输出结果是,列表的191个元素,对应评论总页数,如果读者重新运行了爬虫,因为新增评论数,此处会不同;每个列表里的元素,也就是表格中该列的每个元素均为字符串;截取前200个字符便于展示:
具体代码如下
191
[{'comment_imgs': '', 'parent_mid': '0', 'news_mid_source': '0', 'rank': '0', 'mid': '5B6B9FA6-777B83B3-68B55EBD-8C5-8E4', 'video': '', 'vote': '0', 'uid': '1756716733', 'area': '广东深圳', 'channel_sourc
同样的 道理,replydict列情况相同。
eval() 一下
用eval()函数将 str 字符串格式转换成 list 或 dict 并由apply应用到相应列上:
具体代码如下
df['jsons'] = df.jsons.apply(lambda x: eval(x))
df['cmntlist'] = df.cmntlist.apply(lambda x: eval(x))
df['replydict'] = df.replydict.apply(lambda x: eval(x))
df.head()
可能大家在表格数据上看不出什么变化,但是其中元素格式已经变化了。
准备工作
这里呢,我们再一次将cmntlist列的数据转换成列表格式,方便后续遍历和提取每条评论相关的数据。
cmntlists[0][0]为第一页第一个元素对应的评论数据,是字典形式,每条评论能拿到的数据就是这些,后面要提取的信息也主要是从这些字段里筛选。
具体代码如下
cmntlists = df['cmntlist'].values.tolist()
print(len(cmntlists),len(cmntlists[0]),cmntlists[0][0])
输出总页数,每页评论数田智航,第一页第一个元素对应的评论数据:
具体代码如下
191
20
{'comment_imgs': '', 'parent_mid': '0', 'news_mid_source': '0', 'rank': '2', 'mid': '5B6B7C0A-315A56DA-184F9F245-8C5-89E', 'video': ''张玮珊 , 'vote': '0', 'uid': '6525940293', 'area': '江苏南京', 'channel_source': '', 'content': '贷款买房你怎么说?[二哈][二哈]', 'nick': '用户6525940293', 'hot': '0', 'status_uid': '1663612603', 'content_ext': ''福州励志中学, 'ip': '49.90.86.218', 'media_type': '0', 'config': 'wb_verified=0&wb_screen_name=用户6525940293&wb_cmnt_type=comment_status&wb_user_id=6525940293&wb_description=&area=江苏南京&wb_parent=&wb_profile_img=http%3A%2F%2Ftvax2.sinaimg.cn%2Fdefault%2Fimages%2Fdefault_avatar_male_50?wx_fmt=gif&wb_time=2018-08-09 07:26:02&wb_comment_id=4271006493624688', 'channel': 'cj', 'comment_mid': '0', 'status': 'M_PASS', 'openid': '', 'newsid_source': '', 'parent': '', 'status_cmnt_mid': '4271006493624688', 'parent_profile_img': '', 'news_mid': '0', 'parent_nick': '', 'newsid': 'comos-hhkuskt2879316', 'parent_uid': '0', 'thread_mid': '0', 'thread': '', 'level': '0', 'against': '1533770764'分割人生 , 'usertype': 'wb', 'length': '17', 'profile_img': 'http://tvax2.sinaimg.cn/default/images/default_avatar_male_50?wx_fmt=gif', 'time': '2018-08-09 07:26:04', 'login_type': '0', 'audio': '', 'agree': '2'}
将上述操作后cmntlists就是嵌套列表,为了后续遍历提取数据方便,将其整合成一个列表,每个元素就是一条评论的格式,刘进荣直接用sum()函数即可,举个栗子,下面的代码结果是[1, 2, 3, 2, 1]:
sum([[1,2],[3,2,1]], [])

用到cmntlists上
cmntlist = sum(cmntlists, [])
print(len(cmntlist))
print(cmntlist[0])
输出总评论数和全部评论里的第一条,准确的说是时间最近的一条评论,为字典格式,所需要提取的数据就都在这里了,可自行选出感兴趣的参数进行提取:
具体代码如下
3743
Out[15]:
{'against': '1533770764',
'agree': '2',
'area': '江苏南京'嫁给袁朗 ,
'audio': '',
'channel': 'cj',
'channel_source': '',
'comment_imgs': '',
'comment_mid': '0',
'config': 'wb_verified=0&wb_screen_name=用户6525940293&wb_cmnt_type=comment_status&wb_user_id=6525940293&wb_description=&area=江苏南京&wb_parent=&wb_profile_img=http%3A%2F%2Ftvax2.sinaimg.cn%2Fdefault%2Fimages%2Fdefault_avatar_male_50?wx_fmt=gif&wb_time=2018-08-09 07:26:02&wb_comment_id=4271006493624688',
'content': '贷款买房你怎么说?[二哈][二哈]',
'content_ext': '',
'hot': '0',
'ip': '49.90.86.218',
'length': '17',
'level': '0',
'login_type': '0',
'media_type': '0',
'mid': '5B6B7C0A-315A56DA-184F9F245-8C5-89E',
'news_mid': '0',
'news_mid_source': '0'王奉春 ,
'newsid': 'comos-hhkuskt2879316',
'newsid_source': '',
'nick': '用户6525940293',
'openid': '',
'parent': '',
'parent_mid': '0',
'parent_nick': '',
'parent_profile_img': '',
'parent_uid': '0',
'profile_img': 'http://tvax2.sinaimg.cn/default/images/default_avatar_male_50?wx_fmt=gif',
'rank': '2',
'status': 'M_PASS',
'status_cmnt_mid': '4271006493624688',
'status_uid': '1663612603',
'thread': '',
'thread_mid': '0',
'time': '2018-08-09 07:26:04',
'uid': '6525940293',
'usertype': 'wb',
'video': '',
'vote': '0'}
细心的朋友们应该能看到评论的数据信息中不仅有城市数据,而且还有相关的 IP 数据,因为知道只有 ip 查询的网站,所以我们这次再写个爬虫 将 IP 查询返回来的数据信息进行一并存储。并且打算结合地理位置数据和评论时间可以绘制下评论变化的全国热力图等的利于观察的直观的图,示例如下,很酷炫,实现方式却很简单安久黑奈 ,手把手教你完成(原本是动态的图,但是小编技艺太low了,没能能过来,说是太大了,所以就静态了,大家先凑活看,后期我会努力的!):
在开始写 IP 查询的爬虫前,我们先输出10条评论里的 IP 和地理数据:

先在网上搜到一个 ip 查询的网站,这是我选择的一个:https://ip.cn/
当然,换成其他网站也是可以的,大家随心就好!
可能您对 ip 查询的原理并不了解黄湄媚 ,也对拿到的数据是否准确也无从考证,本回呢,也仅是根据此信息来挖掘,不过和原本评论数据里自带的城市可以对照下,发现大多是一致的,数据应该还算靠谱。不过科学上网的信息也是不真实的。
后面查询评论里的 ip 时也有不少海外的,不知道是真实的呢,还是科学上网的假象等,无从知晓。最后王依梓,和很多人交流,证实这个信息的真实性是存在问题的。这个在之前的文章也说过的。
点击右键“审查元素” -> Network -> ALL -> 复制需查询的 IP 到输入框并点击查询 -> 找到4中的爬虫入口 URL 格式为https://ip.cn/index.php?ip=49.90.86.218-> 在Preview里找到查询结果的信息在网页源代码里:
于是测试下,先拿到网页源代码:
网页源代码
import requests
def ip2loc(ip):
url = 'https://ip.cn/index.php荒野恶林 ?ip={}'.format(ip)
r = requests.get(url).text
return r
text = ip2loc(cmntlist[0]['ip'])
print(text)
具体查询结果在这部分源代码里:
您所查询的 IP:49.90.86.218
所在的地理位置:江苏省南京市 电信
GeoIP: Nanjing, Jiangsu, China
China Telecom
信息的提取可用正则表达式 re 或 BeautifulSoup ,不过在这里用的是 xpath,(Python爬虫利器三之Xpath语法与lxml库的用法),右键点击“审查元素 -> 点新窗口左上角的鼠标logo ->然后选中网页内容后会自动定位到源代码里的相应位置 -> 右键 ‘Copy’ -> ‘Copy Xpath’,自动生成路径,复制到代码里就可以啦宛如爱情,可以参看下图:
具体代码如下
from lxml import etree
html = etree.HTML(text)
loc = html.xpath('//div[@id="result"]/div/p[2]/code/text()')[0]
tele = html.xpath('//div[@id="result"]/div/p[4]/text()')[0]
geo_ip = html.xpath('//div[@id="result"]/div/p[3]/text()')[0]
print(loc,'*',tele,'*',geo_ip)
能获取到查询结果,表明爬虫代码 OK:
江苏省南京市 电信 * China Telecom * GeoIP: Nanjing, Jiangsu, China
然后测试时发现有些 IP 查询结果可能少些数据,所以异常时返回空字符串,输出前30条测试数据,结果不贴了:
具体代码如下
%%time
# 耗时 Wall time: 36 s
import requests
from lxml import etree
import time
import random
def ip2loc(ip):
url = 'https://ip.cn/index.php?ip={}'.format(ip)
text = requests.get(url).text
html = etree.HTML(text)
try:
loc = html.xpath('//div[@id="result"]/div/p[2]/code/text()')[0]
except:loc=''
try:
geo_ip = html.xpath('//div[@id="result"]/div/p[3]/text()')[0]
except:geo_ip=''
try:
tele = html.xpath('//div[@id="result"]/div/p[4]/text()')[0]
except:tele=''
return loc+' * '+geo_ip+' * '+tele
for num,cmnt in enumerate(cmntlist):
ip_loc = ip2loc(cmnt['ip'])
print(num, cmnt['ip'], cmnt['area'], ip_loc)
if num%5==0:
time.sleep(random.randint(0,2))
if num==30:break
数据提取与保存
需查询3千多个 IP 还蛮耗时的(其实后面也没用到这部分数据,大家可以将 IP 查询部分注释掉,并将 DataFrame 的字段删除即可)。
注意设置间隔时间,以免对 IP 查询的网站造成侵扰。最后遍历评论数据,提取感兴趣的数据,并存储到新的 CSV 中方便后续分析挖掘。
具体代码如下
%%time
import time
import random
sinanews_comments = pd.DataFrame(columns = ['No','page','nick','time','content','area',
'ip'华丽巨蚊 ,'ip_loc','length','against','agree', 'channel'艾特九九,
'hot', 'level', 'login_type', 'media_type'马克土司, 'mid'])
page=1
for num,cmnt in enumerate(cmntlist):
nick = cmnt['nick']
times = cmnt['time'] #命名成 time 会和下面 time.sleep() 冲突 # 所以命名成 times
content = cmnt['content']
area = cmnt['area']
ip = cmnt['ip']
ip_loc = ip2loc(cmnt['ip'])
length = cmnt['length']
against = cmnt['against']
agree = cmnt['agree']
channel = cmnt['channel']
hot = cmnt['hot']
level = cmnt['level']
login_type = cmnt['login_type']
media_type = cmnt['media_type']
mid = cmnt['mid']
print(num+1,page,times,nick,content,area,ip_loc)
sinanews_comments = sinanews_comments.append({'No':num+1,'page':page,'nick':nick,'time':times,'content':content,'area':area,
'ip':ip神话高岚 ,'ip_loc':ip_loc,'length':length,'against':against,'agree':agree,
'channel':channel利德治疗仪,'hot':hot,'level':level,'login_type':login_type,
'media_type':media_type,'mid':mid},ignore_index=True)
if num%30 == 0:
time.sleep(random.randint(0,1))
if int((num+1)%20) == 0:
page += 1
sinanews_comments.to_csv('Sina_Finance_Comments_All.csv', encoding='utf-8', line_terminator=' ')
小结
今天的分享就到这里啦,亲爱的朋友们你们都学会了吗?以上的内容已经完成了数据提取的部分,接下来的文章可以准备开始数据分析、挖掘和可视化等的操作了。当然感兴趣的小伙伴可以根据提供的数据先自己动起来哦,看看到底能不能用 pandas 分析出些什么好玩的内容,到时候大家可以在评论区相互交流哈!有什么疑问以及意见建议等的任何问题随时欢迎在留言区评论留言哦!
本项目从爬虫、数据提取与准备、数据异常发现与清洗、分析与可视化等的代码统一开源在GitHub:DesertsX/gulius-projects,感兴趣的朋友可以先行开始哦,有什么问题,意见,建议,小编都期待这您的回复哦!
雷课:
让教育更有质量,
让教育更有想象!