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

1、简介html

  立刻大四了,最近在暑期实习,在数据挖掘的主业以外,也帮助同事作了不少网络数据采集的内容,接下来的数篇文章就将一一罗列出来,来续写几个月前开的这个网络数据采集实战的坑。正则表达式

 

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

2.1 数据要求windows

  此次咱们须要采集的数据是知名旅游网站马蜂窝下重庆区域内全部景点的用户评论数据,以下图所示:浏览器

  思路是,先获取全部景点的poi ID,即每个景点主页url地址中的惟一数字:cookie

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

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

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

  接着根据获得的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)

运行结果:

最终获得的评论数据集格式以下:

 

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

相关文章
相关标签/搜索