python爬虫(上)--请求——关于旅游网站的酒店评论爬取(传参方法)

来源:互联网 发布:域名导向服务器 编辑:程序博客网 时间:2024/05/16 19:27

前言

最近考试一直都没有时间写这篇总结,现在考试暂告一段落,现在抽空出来写一篇总结,总结一下python爬虫的学习进度。

承接上一篇基于scrapy框架爬虫学习小结,上一篇主要是第二次作业后,“老师说会给我们时间继续完善这个作业,直到可以真的爬到微信朋友圈内容….”,其实之后前面半句是有,但是后面半句真的爬到朋友圈却没有了,老师改变了需求,我们变成了去爬一些旅游网站了。

我们被分派到的任务是:研究分析携程,艺龙,去哪儿,途牛和驴友的爬去规则分析以及爬取他们的酒店评论,我分配到的是携程和艺龙的酒店评论的爬取。

刚开始是我想用之前用过的scrapy来做这个爬取,但是不知道是不是我用的不太熟练,了解不深入,导致一些问题我自己没有办法处理,而且上网搜索的一些答案我都看不懂,之后我才直到,scrapy如果自己没有一定实力,排错很困难。

所以还是回到底层python爬虫实现,凡是要一步步来,因为如果我发现网上许多python爬虫都是用urllib,urllib2,bs4,Request等库来实现的,所以先抛开scrapy框架来,回归一些更加基础的爬虫相关库的学习。
(在进行下面之前请执行pip list查看自己是否已经安装了beautiful soup,lxml,Request和scrapy等第三方库,urllib,urllib2,re和json都是内置库不用考虑,如果没有上面四个第三方库,请用pip install之)

分析要爬取的网站

①携程

http://hotels.ctrip.com/hotel/

上面这个是携程酒店页,广度优先的话就是这一层先完成爬去每个酒店评论页的URL,携程酒店页长这样:
这里写图片描述

然后再在每个url里面找到评论数据来源再抓下来,这里我们选择上图第一个酒店桔子酒店(北京天宁寺店)为例子,点进去长这样:

这里写图片描述

看到图中酒店评论(1423)没有,点开看看:
这里写图片描述

貌似我们的要的评论就近在咫尺,但是当我们打开网页源码我们发现它的评论只有第一页的5条,后面的没有了,那么剩下的评论(第2,第3页…)又是怎么获得的呢?

AJAX:这里就得说说AJAX了,本身什么意思我就不详细说了,我们只要知道AJAX技术是一种异步加载数据的方式,在原来静态网页的天下,每次点击都要重新从服务器再次请求整个网页下来,而其实中间我们需要更新的数据只有一小部分(如果原来请求整个网页是100%的话,那么这一小部分只有5%),这样就造成了大量的带宽浪费,AJAX就是当需要更新部分数据时候,只从服务器加载这一小部分数据,而不用把整个页面又重新下载一遍,AJAX属于前后端数据交互技术,那么我们又要怎么观察AJAX的交互呢?又得如何获得这些动态加载的酒店评论数据呢?我自己用的是chrome所以推荐chrome浏览器自带的开发者工具【Ctrl】+【Shift】+【I】,就可以调出来了,长这样:

这里写图片描述

图中我们可以看到现在是评论的第二页,右边啥都没有,先点到Network,再选择筛选器,原来是【All】,现在换成【XHR】(大部分重新加载的AJAX在浏览器中都属于XHR类,当然有部分还可能在JS 类中),这样更加方便找出通过AJAX加载的更新数据,接下就刷新一下页面,让它重新加载第二页评论,结果如图:

这里写图片描述

P.s.我这里试了一下,直接刷新会回到最开始的这个页面。最好的办法就是,先点进酒店评论(1420),此时为第一页,打开chrome的开发者工具,选择XHR筛选器,然后点击下一页,就会得到一模一样的上图

我们可以看到,在右侧的这个URL的内容里面有很多有价值的信息,有Request URL,往下翻,可以看到Reques Headers(Cookie,Referer,User-Agent等伪装头部必备元素~)内容,这里上张图更清晰:

这里写图片描述

一般来说,通过Request URL,我们就可以抓到更新加载的评论数据了,但是这个URL打开是这样的,一片空白,不对啊,以往经验来说应该是有显示出json格式的数据的!!后面我以为是请求头request headers或者是referer(有些网站会检查这个请求来源URL,这个referer记录的就是请求来源URL)的伪装问题,但是写上去了问题依旧,但是通过Preview倒是能够看到加载的内容:

这里写图片描述

我这里纠结了好几天,也尝试很多办法,可能携程有特殊的反爬虫手段?现在目测只能后面再试试通过Selenium+PhantomJS(无头浏览器)模仿用户正常请求来试试了,但是Selenium也要做功课的呀,外加这篇讲传参的就不涉及了。考虑到最近要准备期末考试了,所以暂时把携程酒店评论抓取的工作挂起,以后有空再折腾- -b

P.s.今天在开发者头条看到携程酒店研发部研发经理崔广宇的一个分享,感觉之后可以参考再分析

②艺龙

艺龙总的来说给我的感觉没有携程那么复杂,可能携程太出名了吧,又广告,各种耳闻。

http://hotel.elong.com

上面这个是艺龙酒店页面,我们以广州的酒店为例子,谁叫我们是广州仔呢,广州的话就是这个:

http://hotel.elong.com/guangzhou/

和携程一样又是一列的酒店,长这样:

这里写图片描述

我们也可以广度优先先爬去每个酒店的URL,不过这个先放着,我们先看看点进去每个酒店的评论页面,先分析一番,这里我们以上图中的广州珠江新城希尔顿欢朋酒店为例,下面就是点进去,点击点评,呈现出来长这样:

这里写图片描述

惯例对比网页源码和网页内容(这里指评论),我们发现和携程一样,艺龙酒店评论页面的源代码中只有5条评论,其他估计也是通过AJAX异步加载的,通过chrome的开发者工具分析交互过程,果然如此:

这里写图片描述

上图中是我通过分析评论加载过程得出的,中间省略了一些步骤。

P.s.强烈建议你花点时间琢磨一下如何利用开发者工具分析交互过程,这有助于你快速找出AJAX加载地址URL

详细的信息:
这里写图片描述

这里写图片描述

把General下的Request URL复制粘贴到浏览器地址栏,打开,会有这样的返回:

这里写图片描述

和携程不一样,我们直接打开AJAX请求的URL能够如期地得到我们想要的json格式数据,接下来我们请求这个URL就可以了,不用像静态网页那样通过re(正则表达式)或者xpath定位到某个标签了。

做到这里,貌似我们已经找到我们想要的数据了,别着急,我们再来分析一下这个AJAX请求的URL的规律:

http://hotel.elong.com/ajax/detail/gethotelreviews/?hotelId=91245064&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467394392854

1.我注意到上面的URL中有hotelId字段,这里是91245064,和原来这个【http://hotel.elong.com/guangzhou/91245064/】中的数字一样,我就想是不是每个酒店都有属于自己的一个编号,那么为了验证我的想法,我就打开了【http://hotel.elong.com/guangzhou/32001199/】——广州东山宾馆的页面,同样出现了32001199这一串数字:

这里写图片描述

我们可以看到,这次AJAX请求的URL是:

http://hotel.elong.com/ajax/detail/gethotelreviews?hotelId=32001199&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467395437371 

hotelId是32001199和原本【http://hotel.elong.com/guangzhou/32001199/】 中的一样,所以我基本确定,这串数字就是每个酒店都有的编号。

知道这个又有什么意义?首先知道了酒店有自己的酒店编号,然后又知道了原来通过AJAX请求的URL中的hotelId是等于这个这个酒店编号的,那么我们可不可以只是通过修改这个hotelId来获取不同酒店的酒店评论呢?答案是肯定的,我就不贴试验图了,你们可以自己做试验。

2.再回来看1里面的hotelId=91245064的那个AJAX地址:

http://hotel.elong.com/ajax/detail/gethotelreviews?hotelId=91245064&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467396059996 

我们发现pageIndex=1,学过英文的我们能猜到pageIndex应该就是指页码,对比一下这个URL打开的json中的评论和原页面中第一页的评论内容,发现一致,修改pageIndex=2,回车,发现重新加载,内容改变,再对比重新加载的json数据内容和原页面中第二页的评论内容,发现也是一致的,基本可以肯定pageIndex的值就是确定请求第几页的值的字段

3.通过上面两点对AJAX请求的URL地址的分析,我们知道了我们只要改变hotelId和pageIndex的值就可以请求完艺龙内所有酒店的评论数据,不过如果要用程序实现,我们还需要直到所有的hotelId和每一个酒店内评论最大页面数量

程序实现

1.第一步要做的是获取全部酒店hotelId
(凡事都得小范围试验先,所以还是先拿广州开刀,试着抓第一页全部的酒店编号)

#! usr/bin/python#coding:utf-8import urllibimport urllib2import jsonfrom  bs4 import BeautifulSoup as bsimport re'''crawl page from hotel.elong.com'''#this part can be replaced by requests place="guangzhou/"url="http://hotel.elong.com/"+place#headers camouflageuseragent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36'headers={    'User-Agent':useragent}req=urllib2.Request(url,"",headers)res=urllib2.urlopen(req)data=res.read()res.close()'''parse and select what i need'''#BeautifulSoup(data) will transform [data] in unicode automaticallysoup=bs(data)hotellinks=[]#define a list for hotellinkhotelnumbers=[]#define a list for hotelbumberfor tagdiv in soup.find_all('div',class_='h_info_base'):    for tagp in tagdiv.find_all('p',class_='h_info_b1'):         for taga in tagp.find_all('a'):                hotellink=taga.get('href')            #throw every hotellink to hotellinks list            hotellinks.append(hotellink)for hotellink_to_hotelnumber in hotellinks:    for hotelnumber in re.findall(r'/\d*/',hotellink_to_hotelnumber):        for hotelnumber_again in re.findall(r'\d*',hotelnumber):            hotelnumbers.append(hotelnumber_again)print hotellinks,"\n"print hotelnumbers,"\n" 

以下为输出结果为第一页的全部酒店编号,以及URL路径:

这里写图片描述

P.s.酒店列表翻页也用了AJAX技术实现动态加载,但是和上面谈论的不一样的是,之前谈论的都是GET方法,但是这里POST方法,有点不一样,还没有深入研究,不过我知道的一点是,如果你直接找到它AJAX请求URL,然后直接打开是获取不了数据的,原因现在推测是请求头的“Content-Type:application/x-www-form-urlencoded;”这里出现的,更加准确的原因得继续研究实验POST方法才能得出。
【也是尚未完成,没有完成后面全部页面酒店编号(hotelnumber_list)和路径的抓取,待续】

2.第二步获取每个酒店的评论页面最大值(Pgnum_max)
【未完成,待续】

3.最后获取酒店评论信息

由于前面两部已经获取了酒店编号列表和每个酒店评论页最大值,那么这一步就是基于hotelnumber_list和Pgnum_max做的一个函数(粗略示范一下):

#!/usr/bin/env python# -*- coding:utf-8 -*-import sysreload(sys)sys.setdefaultencoding('utf-8')import timeimport osfrom os.path import existsimport jsonimport codecsimport gzipimport StringIOimport urllibimport urllib2import redef getComment(hotelnumber,Pgnum_max):    for page in xrange(Pgnum_max):        Comments=[]        page=str(page)        print "第"+page+"页"        url="http://hotel.elong.com/ajax/detail/gethotelreviews/?hotelId="+hotelnumber+"&recommendedType=0&pageIndex="+page+"&mainTagId=0&subTagId=0&_=1468730838292"         headers={            'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36',            'Accept':'application/json, text/javascript, */*; q=0.01',            'Accept-Language':'zh-CN,zh;q=0.8',            'Accept-Encoding':'gzip, deflate, sdch',            'Connection':'keep-alive',            'X-Requested-With':'XMLHttpRequest',            #'Referer':'http://hotel.elong.com/guangzhou/32001005/',            'Host':'hotel.elong.com'        }        #请求AJAX        req=urllib2.Request(url,None,headers)        res=urllib2.urlopen(req)        data=res.read()        res.close()        #因为data有压缩所以要从内存中读出来解压        data=StringIO.StringIO(data)        gz=gzip.GzipFile(fileobj=data)        ungz=gz.read()        #正则提取        pattern_Comment=re.compile(r'"content":"\W*"')        #仍不能完全匹配到所有用户,这里只是匹配到中文名的用户,可能要用到非贪婪模式!        pattern_username=re.compile(r'"nickName":"\W*"')        pattern_CommentTime=re.compile(r'"createTimeString":"....-..-..\s..:..:.."')        for username in re.findall(pattern_username,ungz):            # print type(username)            # print username,'\n'            Comments.append(username)        for CommentTime in re.findall(pattern_CommentTime,ungz):            # print type(CommentTime)            # print CommentTime,'\n'            Comments.append(CommentTime)        for Comment in re.findall(pattern_Comment,ungz):             # print Comment,'\n'            # print type(Comment)            Comments.append(Comment)        #保存成HTML        comment=",".join(Comments)#list转str        cdir = u'./Comments/'        if not exists(cdir):            os.makedirs(cdir)        f=codecs.open(cdir+u'page'+page+u'.txt','wb+','utf-8')        f.write(comment)'''程序入口'''#前面也是没有做完,所以酒店编号列表先构造一个hotelnumber_list=['32001005']#前面没有抓到最大评论页面数,先假定最大有200页Pgnum_max=2for hotelnumber in hotelnumber_list:    getComment(hotelnumber,Pgnum_max)

下面是用存储的page0.txt(商品编号为32001005的第一页信息)作为示范,吐槽一下自己,因为偷懒所以直接在windows下开putty用vim打开的page0.txt截的图,现在贴上去后觉得丑死了….
这里写图片描述

免责声明

本篇文章只是为了分享技术和共同学习为目的,并不出于商业目的和用途,也不希望用于商业用途,特此声明。

0 0
原创粉丝点击