前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你完成一个数据科学小项目(2):数据提取、IP查询

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

作者头像
古柳_DesertsX
发布2018-08-21 14:57:28
4980
发布2018-08-21 14:57:28
举报
文章被收录于专栏:Data Analysis & Viz

前言

本系列将全面涉及本项目从爬虫、数据提取与准备、数据异常发现与清洗、分析与可视化等细节,并将代码统一开源在GitHub:DesertsX/gulius-projects ,感兴趣的朋友可以先行 star 哈。

请先阅读“中国年轻人正带领国家走向危机”,这锅背是不背? 一文,以对“手把手教你完成一个数据科学小项目”系列有个全局性的了解。上一篇文章(1)数据爬取里我讲解了如何用爬虫爬取新浪财经《中国年轻人正带领国家走向危机》一文的评论数据,其中涉及的抓包过程是蛮通用的,大家如果想爬取其他网站,也会是类似的流程,当然介绍的比较粗浅,可自行深入扩展学习。

数据提取

读取数据

读取前10行数据,每行是评论区一页包含的数据:

代码语言:javascript
复制
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() 实现将列表样、字典样的字符串转换成列表或字典:

代码语言:javascript
复制
cmntlist = df.cmntlist.values.tolist()
print(len(cmntlist))
print(type(cmntlist[0])) # str 字符串格式
#print(cmntlist[0]) # 篇幅太长占地方
print(cmntlist[0][:200]) # 截取前200字符展示

输出结果是,列表共191个元素,对应评论总页数,如果读者重新运行了爬虫,因为新增评论数,此处会不同;每个列表里的元素,也就是表格中该列的每个元素均为字符串;截取前200个字符便于展示:

代码语言:javascript
复制
191
<class 'str'>
[{'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 应用到相应列上:

代码语言:javascript
复制
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] 为第一页第一个元素对应的评论数据,是字典形式,每条评论能拿到的数据就是这些,后面要提取的信息也主要是这些字段里筛选。

代码语言:javascript
复制
cmntlists = df['cmntlist'].values.tolist()
print(len(cmntlists),len(cmntlists[0]),cmntlists[0][0])

输出总页数,每页评论数,第一页第一个元素对应的评论数据:

代码语言:javascript
复制
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.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.gif', 'time': '2018-08-09 07:26:04', 'login_type': '0', 'audio': '', 'agree': '2'}

将上述操作后cmntlists就是嵌套列表,为了后续遍历提取数据方便,将其整合成一个列表,每个元素就是一条评论的格式,直接用sum()函数即可,举个栗子,下面的代码结果是[1, 2, 3, 2, 1]

代码语言:javascript
复制
sum([[1,2],[3,2,1]], [])

用到 cmntlists上:

代码语言:javascript
复制
cmntlist = sum(cmntlists, [])
print(len(cmntlist)) 
print(cmntlist[0])

输出总评论数和全部评论里的第一条,准确的说是时间最近的一条评论,为字典格式,所需要提取的数据就都在这里了,可自行选出感兴趣的参数进行提取:

代码语言:javascript
复制
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.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.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 查询返回的数据一并进行存储。并且打算结合地理位置数据和评论时间可以绘制下评论变化的全国热力图等,示例如下,很酷炫,实现方式却很简单,手把手教你完成:(送福利)BDP绘制微博转发动态热力图。看图里的时间也正好是一年前了,时光荏苒.....

再开始写 IP 查询的爬虫前,先输出10条评论里的 IP 和地理数据:

代码语言:javascript
复制
for num,cmnt in enumerate(cmntlist):
    print(cmnt['ip'], cmnt['area'])
    if num==10:break

代码语言:javascript
复制
49.90.86.218 江苏南京
59.44.235.75 辽宁铁岭
110.53.17.193 湖南怀化
117.136.32.92 广东广州
49.66.22.166 江苏无锡
117.136.0.149 北京
106.122.202.117 福建厦门
106.122.202.117 福建厦门
221.220.138.231 北京
39.68.204.68 山东济宁
219.232.34.181 北京

网上搜到一个 ip 查询的网站:https://ip.cn/ 换成其他网站亦可。

ip 查询的原理并不了解,拿到的数据是否准确也无从考证,本回也仅是根据此信息来挖掘,不过和原本评论数据里自带的城市可以对照下,发现大多是一致的,数据应该还算靠谱。不过听 @lxghost 说也可能 IP 是假的,自己访问外国网站时的 IP 也不准确。

后面查询评论里的 ip 时也有不少海外的,不知道是真实的呢,还是访问外国网站的假象等,无从知晓。

右键“审查元素” -> Network -> ALL -> 复制需查询的 IP 到输入框并点击查询 -> 找到4中的爬虫入口 URL 格式为https://ip.cn/index.php?ip=49.90.86.218 -> 在Preview里找到查询结果的信息在网页源代码里:

于是测试下,先拿到网页源代码:

代码语言:javascript
复制
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)

具体查询结果在这部分源代码里:

代码语言:javascript
复制
<div id="result"><div class="well"><p>您查询的 IP:<code>49.90.86.218</code></p><p>所在地理位置:<code>江苏省南京市 电信</code></p><p>GeoIP: Nanjing, Jiangsu, China</p><p>China Telecom</p></div></div>

信息提取可用正则表达式 re 或 BeautifulSoup ,不过这里用的是 xpath,(Python爬虫利器三之Xpath语法与lxml库的用法 ),右键“审查元素 -> 点新窗口左上角的鼠标logo ->然后选中网页内容后会自动定位到源代码里位置 -> 右键 ‘Copy’ -> ‘Copy Xpath’,自动生成路径,复制到代码里即可:

代码语言:javascript
复制
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:

代码语言:javascript
复制
江苏省南京市 电信 * China Telecom * GeoIP: Nanjing, Jiangsu, China

然后测试时发现有些 IP 查询结果可能少些数据,所以异常时返回空字符串,输出前30条测试数据,结果不贴了:

代码语言:javascript
复制
%%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 中方便后续分析挖掘。

代码语言:javascript
复制
%%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='\r\n')

小结

以上就完成了数据提取部分,下一篇文章就可以开始数据分析、挖掘和可视化了。感兴趣的小伙伴可以根据提供的数据先玩耍起来,看看能不能用 pandas 分析出些什么好玩的内容哈。

本项目从爬虫、数据提取与准备、数据异常发现与清洗、分析与可视化等的代码统一开源在GitHub:DesertsX/gulius-projects ,感兴趣的朋友可以先行 star 哈。

同步分享平台通知: 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1yx743qjn6g0c

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.08.16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 数据提取
    • 读取数据
      • eval() 一下
        • 准备工作
        • 数据提取与保存
        • 小结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档