作者 | zuobangbang

 来源 | zuobangbang

马蜂窝之旅游问答

上图为马蜂窝的旅游问答页(http://www.mafengwo/wenda/area-10206.html?sFrom=mdd),通过不断点击加载更多发送ajax请求更新页面,通过抓包,可以得到获取回答内容的接口,这是一个get请求,通过更换page来不断的获取新的内容。

不过最大请求page为24,当page>24时,会返回空值。一共只能返回500不到的数据量。所以可以自己在搜索是添加标签,如澳门美食。

这样就可以增加获取到的数据量噢。

import requests	
url = "http://www.mafengwo/qa/ajax_qa/more"	
querystring = {"type":"0","mddid":"10206","tid":"","sort":"1","key":"","page":"1","time":""}	
headers = {	
    'Pragma': "no-cache",	
    'Accept-Encoding': "gzip, deflate",	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Accept': "application/json, text/javascript, */*; q=0.01",	
    'Referer': "http://www.mafengwo/wenda/area-10206.html?sFrom=mdd",	
    'X-Requested-With': "XMLHttpRequest",	
    'cache-control': "no-cache",	
    'Postman-Token': "b0c95558-c509-4752-b49a-d80086e0a15a"	
    }	
response = requests.request("GET", url, headers=headers, params=querystring)	
print(response.text)

马蜂窝之游记

以成都游记举个栗子(http://www.mafengwo/travel-scenic-spot/mafengwo/10035.html)。马蜂窝的游记有两个接口,一个是最热游记,一个是最新游记。最热游记只给提供300页共3000条;最新游记则是有多少给你看多少。所以只需爬取最新游记就可以全部抓取。

这是一个post请求,一共有十个参数,_sn参数可以不用更换,就可以不断改变page获得内容。

也就是说,将page赋值2534的同时不改变_sn的值也可以获取到游记内容。

import requests	
url = "http://www.mafengwo/gonglve/ajax.php"	
querystring = {"act":"get_travellist"}	
payload = "mddid=10035&pageid=mdd_index&sort=2&cost=0&days=0&month=0&tagid=0&page=2534&_ts=1553332896149&_sn=a4ad17d822"	
headers = {	
    'Pragma': "no-cache",	
    'Origin': "http://www.mafengwo",	
    'Accept-Encoding': "gzip, deflate",	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Content-Type': "application/x-www-form-urlencoded; charset=UTF-8",	
    'Accept': "application/json, text/javascript, */*; q=0.01",	
    'Cache-Control': "no-cache",	
    'X-Requested-With': "XMLHttpRequest",	
    'Referer': "http://www.mafengwo/travel-scenic-spot/mafengwo/10035.html",	
    'cache-control': "no-cache",	
    'Postman-Token': "74506c28-c314-4238-b55d-b781360f6a2d"	
    }	
response = requests.request("POST", url, data=payload, headers=headers, params=querystring)	
print(response.text)

马蜂窝之美食

关于美食篇,可显示的页面数也做了限制,只能显示20页共300条数据(http://www.mafengwo/cy/10035/)

不过可以通过拼凑url分区获取,以获得大量数据,其中特色和分类这两类不能同时选择,因为它们的id位于同一位置(0-0-id-0-0-1);而商圈类的id位于第二位(0-id-0-0-0-1);所以可以通过在第二位和第三位平凑不同的id号来获取数据,比如(0-46549-7654-0-0-1)来定位春熙路的川菜。

在不断点击下一页,可以得知这是一个get请求,仅改变第六位(0-id-id-0-0-page)来翻页,如春熙路川菜第十五页:http://www.mafengwo/cy/10035/0-46549-7654-0-0-15.html

当然如果想要得到某家店的详细信息,一定需要进入其详情页。(ul.poi-list > li:nth-child(1) > div.title > h3 > a)

import requests	
url = "http://www.mafengwo/cy/10035/"	
headers = {	
    'Connection': "keep-alive",	
    'Upgrade-Insecure-Requests': "1",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",	
    'Referer': "http://www.mafengwo/cy/10035/0-0-9472-0-0-1.html",	
    'Accept-Encoding': "gzip, deflate",	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'cache-control': "no-cache",	
    'Postman-Token': "3670100a-7683-4e61-aeb1-b71bd4f1d6bd"	
    }	
response = requests.request("GET", url, headers=headers)	
print(response.text)

在这个页面可以得到各个标签的id~~~

马蜂窝之蜂蜂点评

通过抓取美食访问各个商户的详情页,发现评论数据都放在了蜂蜂点评中(http://www.mafengwo/poi/87950.html)

要抓取它的点评数据还是挺困难的,而且它只显示五页

点击下一页,一共五次得到下面的请求,所有的评论数据都在这一类url中。在这里就和游记有一些不同,这里的_sn必须和params以及_ts保持一致。如果不一致就会返回空值。所以要获得评论数据就必须找出_sn是如何生成的。

找到这个js文件http://js.mafengwo/js/hotel/sign/index.js?1552035728。在第363行和第364行打上断点,然后点击下一页。

第363行会生成一个json变量 _0xe7fex39。在第364行,首先运行这一部分:(JSON[__Ox2133f[60]](_0xe7fex39) + _0xe7fex34)即生成一个字符串类型的变量:"{"_ts":"1553500557401","params":"{\"poi_id\":\"87950\",

\"page\":1,\"just_comment\":1}"}c9d6618dbc657b41a66eb0af952906f1"

然后再通过函数_0xe7fex2生成32位的字符串;最通过slice(2,12)函数对其进行切割,获得2-12的字符串,这个就是_sn的值。

查找这个函数发现_0xe7fec函数将变量生成一个长度为4的数组;在通过_0xe7fex10函数将数组生成对应的字符串;最后将_0xe7fex15拼接起来。

而拼接后的这串字符的2-12位就是_sn值。这整个加密过程就是常说的md5加密。右边的加密结果和_0xe7fex15拼接起来的字符串是完全一样的;

此时生成的_sn值bc28b3ed57就是对参数进行md5加密取2-12位的结果。

得到_sn值后,评论数据就可以很容易就得到。这里要注意的是,在请求是必须把url拼接完整,否则只会返回一点点数据。另外在生成_sn时,参数qdata必须保持一致,是双引号就是双引号,有反斜杠就要有反斜杠,不然得不到正确的_sn值。

import hashlib	
import requests	
def par(t):	
    hl = hashlib.md5()	
    hl.update(t)	
    return hl.hexdigest()[2:12]	
page=1	
t=1553500557401	
qdata='{"_ts":"'+str(t)+'","params":"{\\"poi_id\\":\\"87950\\",\\"page\\":'+str(page)+',\\"just_comment\\":1}"}c9d6618dbc657b41a66eb0af952906f1'	
sn=par(qdata.encode('utf-8'))	
print(sn)	

	
url = "http://pagelet.mafengwo/poi/pagelet/poiCommentListApi?"	

	
querystring = {"callback":"jQuery181011036861119045205_1553502048335",	
               "params":"%7B%22poi_id%22%3A%2287950%22%2C%22page%22%3A{}%2C%22just_comment%22%3A1%7D".format(str(page)),	
               "_ts":t,	
               "_sn":sn,	
               "_":t+1}	

	
headers = {	
    'Referer': "http://www.mafengwo/poi/87950.html",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"	
    }	
for key,value in querystring.items():	
    url=url+key+'='+str(value)+'&'	
url=url[:-1]	
response = requests.request("GET", url, headers=headers)	
print(response.text)

既然可以构建参数访问前五页的数据,那是否可以通过这种方式访问第六页呢?将page改为6是可以成功获取到数据的,抽取page=6的第一条评论的部分和第五面第一条对比一下。果真是想太多。。。。。。

马蜂窝之景点

关于马蜂窝的景点数据,也是需要各个参数和_sn保持一致的。这里以四川的景点为例说明。

先抓包,在xhr中点击一次下一页,就有生成三个页面。其中所需要的内容在router.php中。

跟之前一样,在第363行和第364行打上断点,对变量进行md5加密。

可以看到下列结果和上图的_sn值是完全一样!

import hashlib	
import requests	
def par(t):	
    hl = hashlib.md5()	
    hl.update(t)	
    return hl.hexdigest()[2:12]	
page=3	
t=1553503246638	
qdata = '{"_ts":"' + str(t) + '","iMddid":"12703","iPage":"' + str(page) + '","iTagId":"0","sAct":"KMdd_StructWebAjax|GetPoisByTag"}c9d6618dbc657b41a66eb0af952906f1'	
sn=par(qdata.encode('utf-8'))	
print(sn)	
#e08b27d91f	
url = "http://www.mafengwo/ajax/router.php"	
data = {	
    'sAct': 'KMdd_StructWebAjax|GetPoisByTag',	
    'iMddid': '12703',	
    '_ts': t,	
    'iPage': page,	
    'iTagId': '0',	
    '_sn': sn	
}	
headers = {	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"	
    }	
response = requests.request("POST", url, headers=headers,data=data)	
print(response.text)

再比如这个页面,当你点击时就会生成,其_sn也通过md5生成。虽然这个页面没有返回什么需要的内容,但可以巩固一下加密方法。

马蜂窝之当地玩乐

关于马蜂窝的当地玩乐部分(http://www.mafengwo/localdeals/0-0-M12703-0-0-0-0-0.html),没有页数的限制,可以不断的请求下一页。

每点击一次下一页,在xhr中就会新生成两个页面,其中返回数据的url是一个非常简单的get请求,只需要改变page的值即可。

请求第37页,可以看到返回的结果和页面是完全一样的

import requests	
url = "http://www.mafengwo/localdeals/ajax_2017.php"	
querystring = {"act":"GetContentList","tag_group[9521][]":"all","tag_group[9365][]":"all","reduce[]":"all","booking_days[]":"all","from":"NaN","kw":"","to":"M12703","salesType":"NaN","page":"37","group":"NaN","sort":"smart","sort_type":"desc","limit":"20","booking_days%5B%5D":"all","reduce%5B%5D":"all","tag_group%5B9365%5D%5B%5D":"all","tag_group%5B9521%5D%5B%5D":"all"}	
headers = {	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Accept': "application/json, text/javascript, */*; q=0.01",	
    'cache-control': "no-cache",	
    'Postman-Token': "c5ece4e3-cf93-4522-a9a6-0b06397ec81d"	
    }	
response = requests.request("GET", url, headers=headers, params=querystring)	
print(response.text)

马蜂窝之验证码

打开酒店预订页面时,会有一个验证码跳出,再进入酒店页面前必须要先通过这个验证码。

验证码在index这个页面,通过get请求将验证码图片保存到本地,同时需要保存这个网页的cookie。因为在发送验证码的时候需要这个cookie来提供依据,可以理解为一张图片对应一个cookie;通过cookie来验证是否是这张图片。

现在输入正确的验证码,找到如下所示的这个url。这个url通过ccode,_ts,_sn三个参数拼接而成;ccode就是你要输入的验证码,_ts就是时间戳。现在只需要把_sn构建出来就可以了。

还是在第363和364打上断点,此时的_0xe7fex39为{ccode:"fisy",_ts:1553507570004};然后对加上_0xe7fex34的字符串进行md5加密即可。

对比验证码验证参数,可以看到是完全一样的。

import hashlib	
import requests	
import time	
def par(t):	
    hl = hashlib.md5()	
    hl.update(t)	
    return hl.hexdigest()[2:12]	
headers = {	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"	
    }	
purl='http://www.mafengwo/hotel/captcha/index'	
html=requests.get(purl,headers=headers)	
with open('img.jpg', 'wb') as f:	
    image = html.content	
    f.write(image)	
    f.close()	
code = input("请输入验证码")	
o=html.cookies	
PHPSESSID=o['PHPSESSID']	
mfw_uuid=o['mfw_uuid']	
oad_n=o['oad_n']	
t=int(time.time()*1000)	
data='{"_ts":"'+str(t)+'","ccode":"'+code+'"}c9d6618dbc657b41a66eb0af952906f1'	
sn=par(data.encode('utf-8'))	
print(sn)	
data={	
'ccode': code,	
'_ts': str(t),	
'_sn': sn	
}	
cookie="PHPSESSID={0}; mfw_uuid={1}; oad_n={2}; uva=s%3A78%3A%22a%3A3%3A%7Bs%3A2%3A%22lt%22%3Bi%3A1552989105%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%3A1552989105%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=5c90bbb0-8d5f-5df8-50bb-acf3b2f39a12; UM_distinctid=1699904eca797-0b6edef1fe2585-36637902-13c680-1699904eca868e; ad_widget_footer_20190314formal_2_other=1%2C1vuh0fz; __mfwothchid=referrer%7Cwww.mafengwo; __mfwlv=1553501567; __mfwvn=6; CNZZDATA30065558=cnzz_eid%3D20394471-1553047971-%26ntime%3D1553506977; __mfwlt=1553508203".format(PHPSESSID,mfw_uuid,oad_n)	
url='http://www.mafengwo/hotel/captcha/check?ccode={0}&_ts={1}&_sn={2}'.format(code,str(t),sn)	
headers = {	
    'Cookie': cookie,	
    'Accept-Encoding': "gzip, deflate",	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Accept': "application/json, text/javascript, */*; q=0.01",	
    'Referer': "http://www.mafengwo/hotel/10035/",	
    'X-Requested-With': "XMLHttpRequest",	
    'Connection': "keep-alive",	
    'cache-control': "no-cache",	
    'Postman-Token': "620f7d41-d1de-4fe1-a69c-5a770227337a"	
    }	

	
x=requests.get(url,headers=headers)	
print(x.text)

返回结果如下:

马蜂窝之酒店

再输入验证码后,就可以请求到包含酒店信息的网址了,这一部分同样也是将参数和_sn配对,如果配对成功就可以返回相应的信息。

还是打开之前的js文件,通过断点调试,得到进行md5加密的字符串。

通过修改里面的参数就可以获得任意网页的酒店信息了。

import hashlib	
import requests	
import time	
def par(t):	
    hl = hashlib.md5()	
    hl.update(t)	
    return hl.hexdigest()[2:12]	
page=1	
t=int(time.time()*1000)	
fromdata='2019-03-28'	
todata='2019-03-29'	
url = "http://www.mafengwo/hotel/ajax.php"	
data='{"_ts":"'+str(t)+'","has_booking_rooms":"1","has_faved":"0","iAdultsNum":"2","iAreaId":"-1","iChildrenNum":"0","iDistance":"10000","iMddId":"10035","iPage":"'+str(page)+'","iPoiId":"","iPriceMax":"","iPriceMin":"","iRegionId":"-1","nLat":"0","nLng":"0","position_name":"","sAction":"getPoiList5","sCheckIn":"'+fromdata+'","sCheckOut":"'+todata+'","sChildrenAge":"","sKeyWord":"","sSortFlag":"DESC","sSortType":"comment","sTags":""}c9d6618dbc657b41a66eb0af952906f1'	
sn=par(data.encode('utf-8'))	
querystring = {"iMddId":"10035",	
               "iAreaId":"-1",	
               "iRegionId":"-1","iPoiId":"","position_name":"","nLat":"0","nLng":"0","iDistance":"10000",	
               "sCheckIn":fromdata,	
               "sCheckOut":todata,	
               "iAdultsNum":"2",	
               "iChildrenNum":"0",	
               "sChildrenAge":"",	
               "iPriceMin":"",	
               "iPriceMax":"",	
               "sTags":"","sSortType":"comment","sSortFlag":"DESC","has_booking_rooms":"1",	
               "has_faved":"0","sKeyWord":"","iPage":str(page),	
               "sAction":"getPoiList5","_ts":str(t),	
               "_sn":sn}	

	
headers = {	
    # 'Cookie': "PHPSESSID=0ltaobjcharadcioh30akag7i7; mfw_uuid=5c90bbb0-8d5f-5df8-50bb-acf3b2f39a12; uva=s%3A78%3A%22a%3A3%3A%7Bs%3A2%3A%22lt%22%3Bi%3A1552989105%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%3A1552989105%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=5c90bbb0-8d5f-5df8-50bb-acf3b2f39a12; UM_distinctid=1699904eca797-0b6edef1fe2585-36637902-13c680-1699904eca868e; ad_widget_footer_20190314formal_2_other=1%2C1vuh0fz; oad_n=a%3A3%3A%7Bs%3A3%3A%22oid%22%3Bi%3A1029%3Bs%3A2%3A%22dm%22%3Bs%3A15%3A%22www.mafengwo%22%3Bs%3A2%3A%22ft%22%3Bs%3A19%3A%222019-03-28+10%3A28%3A46%22%3B%7D; __mfwlv=1553740127; __mfwvn=7; CNZZDATA30065558=cnzz_eid%3D20394471-1553047971-%26ntime%3D1553739181; __mfwlt=1553744294",	
    'Accept-Encoding': "gzip, deflate",	
    'Accept-Language': "zh-CN,zh;q=0.9",	
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",	
    'Accept': "application/json, text/javascript, */*; q=0.01",	
    'Referer': "http://www.mafengwo/hotel/10035/",	
    'X-Requested-With': "XMLHttpRequest",	
    'Connection': "keep-alive",	
    'cache-control': "no-cache",	
    'Postman-Token': "3b29182a-18b2-46a9-8a2a-d3cde4de70f9"	
    }	
response = requests.request("GET", url, headers=headers, params=querystring)	
print(response.text)

这样就可以获取数据啦。

万水千山总是情,点个「好看」行不行。

留言打卡 DAY 12 

今日的留言话题是你对马蜂窝网站中哪部分数据最感兴趣,关于留言打卡的规则可以参考

◆ ◆ ◆  ◆ ◆

长按二维码关注我们


数据森麟公众号的交流群已经建立,许多小伙伴已经加入其中,感谢大家的支持。大家可以在群里交流关于数据分析&数据挖掘的相关内容,还没有加入的小伙伴可以扫描下方管理员二维码,进群前一定要关注公众号奥,关注后让管理员帮忙拉进群,期待大家的加入。

管理员二维码:

猜你喜欢

 

更多推荐

捅马蜂窝啦!!!