专栏首页数据科学学习手札(数据科学学习手札47)基于Python的网络数据采集实战(2)
原创

(数据科学学习手札47)基于Python的网络数据采集实战(2)

一、简介

  马上大四了,最近在暑期实习,在数据挖掘的主业之外,也帮助同事做了很多网络数据采集的内容,接下来的数篇文章就将一一罗列出来,来续写几个月前开的这个网络数据采集实战的坑。

二、马蜂窝评论数据采集实战

2.1 数据要求

  这次我们需要采集的数据是知名旅游网站马蜂窝下重庆区域内所有景点的用户评论数据,如下图所示:

  思路是,先获取所有景点的poi ID,即每一个景点主页url地址中的唯一数字:

这一步和(数据科学学习手札33)基于Python的网络数据采集实战(1)中做法类似,即在下述界面:

翻页抓取对应每个景点poi ID的部分即可:

比较简单,这里不再赘述,最终整理成数据框,景点名和poi ID一一对应。

  接着根据得到的poi ID,再对每一个景点下的评论数据分别进行采集,但和之前遇到的最简单的静态网页不同,这里的评论数据是有js控制的,即当我们在景点页面内点击评论区块的下一页按钮,界面会刷新并显示下一页的评论内容,但浏览器url栏中的url地址并无改变,这就需要用更深入的方式来获取评论区域数据的真实url地址。

2.2 目标url地址的获取

  以洪崖洞页面为例,点击页面内的蜂蜂点评进入评论内容区域:

当我们点击评论数据区域下方的下一页时,评论内容翻页刷新,但浏览器地址栏中的url地址并没有发生改变:

这时我们就需要找到控制评论数据区域的真实请求地址,在浏览器中按下F12,打开开发者工具,点击network项:

选择JS,这时可以发现下面并无内容,因为这里只会记录打开开发者工具后页面内新增的内容,这时我们点击评论区域下方的后一页按钮,随着界面内容的更新,下方network中随即出现了如下内容:

这就是请求评论区域内容的真实url地址,点击它,进入如下内容:

至此,我们便找到了控制评论区域发起请求的真实地址和相关属性,接下来我们先提取一下这些内容中我们需要的部分,为正式的采集做好准备;

2.3 伪装浏览器

  要伪装浏览器,我们需要将上图中的Request Headers下除了cookies的内容复制下来,整理成一个叫做headers的字典如下(其中起关键作用的是User-Agent,其他的可以不记录):

再将Request Headers下的cookies中由;分隔的内容同样整理成一个叫做cookies的字典如下:

这两个参数我们会在requests包中的get方法中传入,接下来我们来观察翻页请求url的规律;

2.4 探索url规律

  我们找到下列内容中红圈指示的地方:

上面红圈中的内容是当前评论区域发起请求的真实url地址,下面红圈的内容是在当前url中的关键参数,很明显,params是一个字典,其中poi_id顾名思义即为当前景点(洪崖洞)对应的poi ID,page对应的则是当前评论内容所在的页数,just_comment这个参数我观察到在任何页中都不变,这里我们让它持续为1即可。

2.5 测试

  了解到上述内容后,结合当前的url地址,可以得到下列替换规则:

http://pagelet.mafengwo.cn/poi/pagelet/poiCommentListApicallback=jQuery181042269431321668516_1534601996232&params=%7B%22poi_id%22%3A%226653%22%2C%22page%22%3A2%2C%22just_comment%22%3A1%7D&_=1534602025986

只需要控制红色区域内容的替换,我们即可实现对评论内容资源的请求,下面我们来做个测试,这里以解放碑(对应poi ID 1690)下第13页评论为例,按照上述规则,我们将网址分成素材和实时参数两部分,下面的url即为通过拼接最终得到的url地址:

url_left = 'http://pagelet.mafengwo.cn/poi/pagelet/poiCommentListApi?callback=jQuery18109093089574473625_1532513669920&params=%7B%22poi_id%22%3A%22'
url_middle = '%22%2C%22page%22%3A'
url_right = '%2C%22just_comment%22%3A1%7D&_=1532513718986'

url = url_left+'1690'+url_middle+'13'+url_right

我们在Python中进行测试,对上述url地址发起请求:

import requests


#设置请求头文件
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36',
    'Accept':'*/*',
    'Accept-Encoding':'gzip, deflate',
    'Accept-Language':'zh-CN,zh;q=0.9',
    'Connection':'keep-alive',
    'Referer':'http://www.mafengwo.cn/poi/6653.html'
}

#设置cookies
cookie = {
    'PHPSESSID':'1nivct21bumab1adia6i7k1a82',
    'mfw_uuid':'5b583768-bf26-19ea-0187-96a00686bd4d',
    'uva':'s%3A78%3A%22a%3A3%3A%7Bs%3A2%3A%22lt%22%3Bi%3A1532508009%3Bs%3A10%3A%22last_refer%22%3Bs%3A6%3A%22direct%22%3Bs%3A5%3A%22rhost%22%3Bs%3A0%3A%22%22%3B%7D%22%3B',
    '__mfwurd':'a%3A3%3A%7Bs%3A6%3A%22f_time%22%3Bi%3A1532508009%3Bs%3A9%3A%22f_rdomain%22%3Bs%3A0%3A%22%22%3Bs%3A6%3A%22f_host%22%3Bs%3A3%3A%22www%22%3B%7D',
    '__mfwuuid':'5b583768-bf26-19ea-0187-96a00686bd4d',
    'UM_distinctid':'164d0987902bb2-0bb3f4e59cf01f-3e3d560e-1fa400-164d098790328c',
    'oad_n':'a%3A3%3A%7Bs%3A3%3A%22oid%22%3Bi%3A1029%3Bs%3A2%3A%22dm%22%3Bs%3A15%3A%22www.mafengwo.cn%22%3Bs%3A2%3A%22ft%22%3Bs%3A19%3A%222018-07-25+16%3A40%3A08%22%3B%7D',
    '__mfwlv':'1532508009',
    '__mfwvn':'1',
    '__mfwlt':'1532508103'

}
url_left = 'http://pagelet.mafengwo.cn/poi/pagelet/poiCommentListApi?callback=jQuery18109093089574473625_1532513669920&params=%7B%22poi_id%22%3A%22'
url_middle = '%22%2C%22page%22%3A'
url_right = '%2C%22just_comment%22%3A1%7D&_=1532513718986'

url = url_left+'1690'+url_middle+'13'+url_right
r = requests.get(url=url, headers=headers, cookies=cookie)

得到网页内容如下:

这里的网页内容还未经过转码,这里我们使用下述方式转码,并将\替换为空字符:

'''对相应的网页内容进行转码'''
html = r.content.decode('unicode-escape').replace('\\','')

得到html为:

可以看到,需要的中文内容已经提取完毕,接下来我们需要做的是对我们感兴趣的内容进行提取,,这里我们感兴趣的是每条评论的文本内容、评分以及评论时间,这里使用正则表达式来提取:

import re
from bs4 import BeautifulSoup

obj = BeautifulSoup(html,'lxml')

# # '''利用findAll定位目标标签及其属性并返回其字符形式结果'''
text = list(obj.findAll('p', {'class': "rev-txt"}))
star = list(obj.findAll('span'))
Time = list(obj.findAll('span', {'class': "time"}))

#将每一条评论对应的内容提取出来
control = 0
for m in range(len(star)):
    try:
        if 'star' in str(star[m]):
            '''设置不同的正则规则来提取目标内容'''
            print(re.findall('[0-5]+', str(star[m]))[0])
            print(re.sub('[a-zA-Z="\-<> \/\n\r]+', '', str(text[control])))
            print(re.findall('<.*?>(.*?)<.*?>',str(Time[control]))[0])
            control += 1
    except Exception as e:
        print('error')

通过上面的测试,我们成功获取到该测试页内的所需内容,下面附上完整采集的代码,只是加上一些错误处理机制、随机暂停防ban机制和一些保存数据的内容:

2.6 完整的采集程序

  正式采集部分沿用前面测试中的思想,具体代码如下:

'''这个脚本用于对JS脚本控制翻页的动态网页进行爬取'''

import requests
import time
import random
from bs4 import BeautifulSoup
import re
import json
import pandas as pd


#设置请求头文件
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36',
    'Accept':'*/*',
    'Accept-Encoding':'gzip, deflate',
    'Accept-Language':'zh-CN,zh;q=0.9',
    'Connection':'keep-alive',
    'Referer':'http://www.mafengwo.cn/poi/6653.html'
}

#设置cookies
cookie = {
    'PHPSESSID':'1nivct21bumab1adia6i7k1a82',
    'mfw_uuid':'5b583768-bf26-19ea-0187-96a00686bd4d',
    'uva':'s%3A78%3A%22a%3A3%3A%7Bs%3A2%3A%22lt%22%3Bi%3A1532508009%3Bs%3A10%3A%22last_refer%22%3Bs%3A6%3A%22direct%22%3Bs%3A5%3A%22rhost%22%3Bs%3A0%3A%22%22%3B%7D%22%3B',
    '__mfwurd':'a%3A3%3A%7Bs%3A6%3A%22f_time%22%3Bi%3A1532508009%3Bs%3A9%3A%22f_rdomain%22%3Bs%3A0%3A%22%22%3Bs%3A6%3A%22f_host%22%3Bs%3A3%3A%22www%22%3B%7D',
    '__mfwuuid':'5b583768-bf26-19ea-0187-96a00686bd4d',
    'UM_distinctid':'164d0987902bb2-0bb3f4e59cf01f-3e3d560e-1fa400-164d098790328c',
    'oad_n':'a%3A3%3A%7Bs%3A3%3A%22oid%22%3Bi%3A1029%3Bs%3A2%3A%22dm%22%3Bs%3A15%3A%22www.mafengwo.cn%22%3Bs%3A2%3A%22ft%22%3Bs%3A19%3A%222018-07-25+16%3A40%3A08%22%3B%7D',
    '__mfwlv':'1532508009',
    '__mfwvn':'1',
    '__mfwlt':'1532508103'

}

'''JS脚本发起的真实的网址请求对应的网址内容模板(及除去几个动态参数之外的死板的url内容)'''
url_left = 'http://pagelet.mafengwo.cn/poi/pagelet/poiCommentListApi?callback=jQuery18109093089574473625_1532513669920&params=%7B%22poi_id%22%3A%22'
url_middle = '%22%2C%22page%22%3A'
url_right = '%2C%22just_comment%22%3A1%7D&_=1532513718986'


data = pd.read_excel(r'C:\Users\windows\Desktop\summer_project\GIS\data\马蜂窝重庆景点评论数据(2018-7-26采集)\chongqing_scene.xlsx')

'''读入poi_id数据以在循环中进行url的构建'''

Q = {}
poi_id_list = []
scene_name = []
for key,value in zip(data['id'],data['景点名称']):
    Q[str(key)] = str(value)
    poi_id_list.append(str(key))
    scene_name.append(value)


comment = []
S = []
id = []
t = []

count = 1
for i in poi_id_list:
    print('{}采集开始'.format(Q[i]))

    '''构造包含poi_id内容的url前半部分内容'''
    url_first = url_left+i+url_middle
    for j in range(1,10000):
        try:
            '''构造包含翻页信息的完整url内容'''
            url_first = url_left + i + url_middle
            url = url_first + str(j) + url_right

            '''向构造好的真实网页发起请求'''
            r = requests.get(url=url, headers=headers, cookies=cookie)

            '''对相应的网页内容进行转码'''
            html = r.content.decode('unicode-escape')

            '''判断当前景点所有有效评论页面是否已被爬取完成'''
            if '暂无内容' in str(html):
                print('本景点评论数据已被爬完!')
                break
            else:
                '''将网页内容中的单\替换成空'''
                html = html.replace('\\', '')
                '''利用bs4对网页内容进行CSS解析'''
                obj = BeautifulSoup(html, 'lxml')

                # # '''利用findAll定位目标标签及其属性并返回其字符形式结果'''
                text = list(obj.findAll('p', {'class': "rev-txt"}))
                star = list(obj.findAll('span'))
                Time = list(obj.findAll('span', {'class': "time"}))

                '''设置一个复杂周密的错误处理机制以防止长时间爬虫任务中可能出现的各种错误中断任务主体'''
                control = 0
                for m in range(len(star)):
                    try:
                        if 'star' in str(star[m]):
                            '''设置不同的正则规则来提取目标内容'''
                            token = re.findall('[0-5]+', str(star[m]))[0]
                            comment.append(re.sub('[a-zA-Z="\-<> \/\n\r]+', '', str(text[control])))
                            t.append(re.findall('<.*?>(.*?)<.*?>',str(Time[control]))[0])
                            S.append(int(token))
                            id.append(i)
                            print('-'*100)
                            print('总第{}条评论被采集'.format(str(count)))
                            print('-' * 100)
                            count += 1
                            control += 1
                        else:
                            pass
                    except Exception as e:
                        pass

            '''设置随机睡眠机制以防止被ban'''
            print('='*100)
            print('{}的'.format(Q[i]),'第{}页被采集完'.format(str(j)))
            print('=' *100)
            time.sleep(random.randint(2,4))
        except Exception as e:
            pass
    print('{}采集结束'.format(Q[i]))



'''写出数据到数据框'''
df = pd.DataFrame({'id':id,
                   'comment':comment,
                   'S':S,
                   'Time':t})

df.to_excel('raw_data.xlsx',index=False)

运行结果:

最终得到的评论数据集格式如下:

  以上就是关于本文的全部内容,如有不解之处,望指出。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • (数据科学学习手札52)pandas中的ExcelWriter和ExcelFile

      pandas中的ExcelFile()和ExcelWriter(),是pandas中对excel表格文件进行读写相关操作非常方便快捷的类,尤其是在对含有多个...

    Feffery
  • (数据科学学习手札13)K-medoids聚类算法原理简介&Python与R的实现

    前几篇我们较为详细地介绍了K-means聚类法的实现方法和具体实战,这种方法虽然快速高效,是大规模数据聚类分析中首选的方法,但是它也有一些短板,比如在数据集中有...

    Feffery
  • (数据科学学习手札49)Scala中的模式匹配

      Scala中的模式匹配类似Java中的switch语句,且更加稳健,本文就将针对Scala中模式匹配的一些基本实例进行介绍:

    Feffery
  • Logstash解析嵌套Json

    由于我们的埋点日志是嵌套json类型,要想最终所有字段展开来统计分析就必须把嵌套json展开。

    神秘的寇先森
  • 万方数据库,文献下载的准备

     ###后来文献下载任务完成了 ,相关的代码,细节,介绍看这篇文章 https://www.jianshu.com/p/134530b46a65

    东风冷雪
  • 使用elasticsearch遇到的一些问题以及解决方法

    1.由gc引起节点脱离集群 因为gc时会使jvm停止工作,如果某个节点gc时间过长,master ping3次(zen discovery默认ping失败重试3...

    小柒2012
  • python3爬虫(4)各种网站视频下载

    理论上来讲只要是网上(浏览器)能看到图片,音频,视频,都能够下载下来,然而实际操作的时候也是有一定难度和技术的,这篇文章主要讲述各个网站视频资源如何下载。

    py3study
  • 强网杯线下Web签到和CVE-2019-9081

    强网杯有一个web题目 序列化getshell 算是签到题目laravel-5.7rce首先先搭建环境

    用户5878089
  • 百家号爬取(2)

    https://mbd.baidu.com/webpage?type=homepage&action=interact&format=jsonp&params=...

    Centy Zhao
  • 【延迟注入】A5站长网某站存在SQL注入漏洞

    A5站长网某站存在SQL注入漏洞(附验证脚本) 详细说明: code 区域 POST /Login/login HTTP/1.1 Host: lianmeng....

    逸鹏

扫码关注云+社区

领取腾讯云代金券