作者 | zuobangbang
来源 | zuobangbang
马蜂窝之旅游问答
上图为马蜂窝的旅游问答页(http://www.mafengwo.cn/wenda/area-10206.html?sFrom=mdd),通过不断点击加载更多发送ajax请求更新页面,通过抓包,可以得到获取回答内容的接口,这是一个get请求,通过更换page来不断的获取新的内容。
不过最大请求page为24,当page>24时,会返回空值。一共只能返回500不到的数据量。所以可以自己在搜索是添加标签,如澳门美食。
这样就可以增加获取到的数据量噢。
import requests
url = "http://www.mafengwo.cn/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.cn/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.cn/travel-scenic-spot/mafengwo/10035.html)。马蜂窝的游记有两个接口,一个是最热游记,一个是最新游记。最热游记只给提供300页共3000条;最新游记则是有多少给你看多少。所以只需爬取最新游记就可以全部抓取。
这是一个post请求,一共有十个参数,_sn参数可以不用更换,就可以不断改变page获得内容。
也就是说,将page赋值2534的同时不改变_sn的值也可以获取到游记内容。
import requests
url = "http://www.mafengwo.cn/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.cn",
'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.cn/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.cn/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.cn/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.cn/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.cn/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.cn/poi/87950.html)
要抓取它的点评数据还是挺困难的,而且它只显示五页
点击下一页,一共五次得到下面的请求,所有的评论数据都在这一类url中。在这里就和游记有一些不同,这里的_sn必须和params以及_ts保持一致。如果不一致就会返回空值。所以要获得评论数据就必须找出_sn是如何生成的。
找到这个js文件http://js.mafengwo.net/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.cn/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.cn/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.cn/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.cn/localdeals/0-0-M12703-0-0-0-0-0.html),没有页数的限制,可以不断的请求下一页。
每点击一次下一页,在xhr中就会新生成两个页面,其中返回数据的url是一个非常简单的get请求,只需要改变page的值即可。
请求第37页,可以看到返回的结果和页面是完全一样的
import requests
url = "http://www.mafengwo.cn/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.cn/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.cn; __mfwlv=1553501567; __mfwvn=6; CNZZDATA30065558=cnzz_eid%3D20394471-1553047971-%26ntime%3D1553506977; __mfwlt=1553508203".format(PHPSESSID,mfw_uuid,oad_n)
url='http://www.mafengwo.cn/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.cn/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.cn/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.cn%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.cn/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
今日的留言话题是你对马蜂窝网站中哪部分数据最感兴趣,关于留言打卡的规则可以参考
◆ ◆ ◆ ◆ ◆
长按二维码关注我们
数据森麟公众号的交流群已经建立,许多小伙伴已经加入其中,感谢大家的支持。大家可以在群里交流关于数据分析&数据挖掘的相关内容,还没有加入的小伙伴可以扫描下方管理员二维码,进群前一定要关注公众号奥,关注后让管理员帮忙拉进群,期待大家的加入。
管理员二维码: