大眾點評之執行緒池實現全站爬取
要想全站爬取,首先需要分商區、菜系,這樣得到的資料才全,不然網站預設只顯示50頁的資料,根本不滿足要求。
第一步,獲取所有商區和菜系的url從http://www.dianping.com/beijing/food這個網站獲取比較簡單,就直接在後面貼程式碼了。
朝外大街: http://www.dianping.com/beijing/ch10/r1466
燒烤:http://www.dianping.com/beijing/ch10/g508
朝外大街燒烤:http://www.dianping.com/beijing/ch10/g508r1466
這樣只要得到後面的那個標識,然後拼接就行。
插播一段很簡單的程式碼(每次複製抓包的請求頭都要手工加引號使他變成字典格式,為什麼不寫一段程式碼自動變成字典格式呢,我見過有些人是使用正則替換,效果都一樣):
headers_ = ''' Accept:*/* Accept-Encoding:gzip, deflate, sdch Accept-Language:zh-CN,zh;q=0.8 Access-Control-Request-Headers:content-type Access-Control-Request-Method:POST Cache-Control:no-cache Connection:keep-alive Host:catfront.dianping.com Origin:http://www.dianping.com Pragma:no-cache Referer:http://www.dianping.com/shop/120054776 User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36''' headers = {line.split(':')[0].strip():line.split(':',1)[1].strip() for line in headers_.split('\n') if line.strip()} print(headers) {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate, sdch', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Content-Type': 'text/plain', 'Host': 'wreport2.meituan.net', 'Origin': 'http://www.dianping.com', 'Pragma': 'no-cache', 'Referer': 'http://www.dianping.com/shop/120054776', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36' }
得到了這樣的字典:
areas = { '朝陽區國貿': 'r2578', '朝陽區雙井': 'r2579', '朝陽區三里屯': 'r2580', '朝陽區對外經貿': 'r2581', '朝陽區酒仙橋': 'r2583', '朝陽區管莊': 'r2584', '朝陽區首都機場': 'r2585', '朝陽區十八里店': 'r2586', '朝陽區北苑家園': 'r2870', '朝陽區十里堡': 'r2871', '朝陽區東壩': 'r7509', '朝陽區孫河': 'r12012', '朝陽區馬泉營': 'r12013', '朝陽區定福莊': 'r12015', '朝陽區四惠': 'r22996', '朝陽區太陽宮': 'r22997', '朝陽區青年路': 'r22998', '朝陽區石佛營': 'r22999', '朝陽區甜水園': 'r23000', '朝陽區慈雲寺/八里莊': 'r23001', '朝陽區工人體育場': 'r23002', '朝陽區百子灣': 'r23003', '朝陽區傳媒大學/二外': 'r23004', '朝陽區雙橋': 'r23005', '朝陽區北京歡樂谷': 'r23006', '朝陽區高碑店': 'r23007', '朝陽區北京東站': 'r23008', '朝陽區霄雲路': 'r23009', '朝陽區藍色港灣': 'r23010', '朝陽區燕莎/農業展覽館': 'r23011', '朝陽區姚家園': 'r23012', '朝陽區十里河': 'r23013', '朝陽區立水橋': 'r23014', '朝陽區小營': 'r23015', '朝陽區北沙灘': 'r23016', '朝陽區大屯': 'r23017', '朝陽區小莊/紅廟': 'r23018', '朝陽區常營': 'r23019', '朝陽區798/大山子': 'r23020', '朝陽區草房': 'r70269', '朝陽區朝陽公園': 'r81430', '朝陽區世貿天階': 'r83302', '朝陽區東大橋': 'r83304', '朝陽區小紅門': 'r85511', '朝陽區廣渠門外': 'r89473', '朝陽區建外大街': 'r1465', '朝陽區大望路': 'r2078', '朝陽區朝外大街': 'r1466', '朝陽區朝陽公園/團結湖': 'r1467', '朝陽區左家莊': 'r1468', '朝陽區亮馬橋/三元橋': 'r1469', '朝陽區亞運村': 'r1470', '朝陽區望京': 'r1471', '朝陽區勁鬆/潘家園': 'r1472', '朝陽區安貞': 'r1473', '朝陽區朝陽其它': 'r1474', '朝陽區芍藥居': 'r70191', '東城區和平里': 'r2591', '東城區東四十條': 'r23021', '東城區雍和宮/地壇': 'r23022', '東城區南鑼鼓巷/鼓樓東大街': 'r23023', '東城區北新橋/簋街': 'r23024', '東城區光明樓/龍潭湖': 'r23025', '東城區沙灘/美術館燈市口': 'r23026', '東城區王府井/東單': 'r1475', '東城區建國門/北京站': 'r1476', '東城區東四': 'r1477', '東城區安定門': 'r1478', '東城區朝陽門': 'r1479', '東城區東直門': 'r2066', '東城區廣渠門內': 'r2590', '東城區左安門': 'r2874', '東城區沙子口': 'r2875', '東城區前門': 'r1503', '東城區崇文門': 'r1504', '東城區天壇': 'r1505', '西城區西四': 'r2593', '西城區月壇': 'r2594', '西城區什剎海': 'r2595', '西城區德外大街': 'r2873', '西城區陶然亭': 'r23027', '西城區南菜園/白紙坊': 'r23028', '西城區西單': 'r1481', '西城區復興門': 'r1482', '西城區阜成門': 'r1483', '西城區西直門/動物園': 'r1484', '西城區新街口': 'r1485', '西城區地安門': 'r1486', '西城區牛街': 'r2596', '西城區虎坊橋': 'r2597', '西城區菜市口': 'r2876', '西城區廣內大街': 'r1499', '西城區廣外大街': 'r1500', '西城區宣武門': 'r1501', '西城區右安門': 'r1994', '海淀區雙榆樹': 'r2587', '海淀區五棵松': 'r2588', '海淀區清河': 'r2589', '海淀區遠大路': 'r2872', '海淀區香山': 'r7510', '海淀區大鐘寺': 'r23029', '海淀區知春路': 'r23030', '海淀區西三旗': 'r23031', '海淀區四季青': 'r23032', '海淀區人民大學': 'r23033', '海淀區萬柳': 'r23034', '海淀區學院橋': 'r23035', '海淀區軍博': 'r23988', '海淀區農業大學西區': 'r23989', '海淀區中關村': 'r1488', '海淀區五道口': 'r1489', '海淀區魏公村': 'r1996', '海淀區北太平莊': 'r1490', '海淀區蘇州橋': 'r1491', '海淀區北下關': 'r1492', '海淀區公主墳/萬壽路': 'r1493', '海淀區紫竹橋': 'r1494', '海淀區航天橋': 'r1495', '海淀區上地': 'r1496', '海淀區頤和園': 'r1497', '海淀區海淀其它': 'r1498', '海淀區田村': 'r70131', '豐臺區北大地': 'r2592', '豐臺區劉家窯': 'r2877', '豐臺區青塔': 'r2878', '豐臺區開陽裡': 'r2879', '豐臺區草橋': 'r2880', '豐臺區看丹橋': 'r2881', '豐臺區花鄉': 'r7040', '豐臺區大紅門': 'r7041', '豐臺區公益西橋': 'r7506', '豐臺區雲崗': 'r7507', '豐臺區盧溝橋': 'r7508', '豐臺區北京西站/六裡橋': 'r23036', '豐臺區分鐘寺/成壽寺': 'r23037', '豐臺區夏家衚衕/紀家廟': 'r23038', '豐臺區馬家堡/角門': 'r23039', '豐臺區麗澤橋/豐管路': 'r23040', '豐臺區總部基地': 'r25600', '豐臺區石榴莊': 'r70275', '豐臺區槐房萬達廣場': 'r70610', '豐臺區方莊': 'r1507', '豐臺區六裡橋/麗澤橋': 'r1508', '豐臺區洋橋/木樨園': 'r1995', '豐臺區豐臺其它': 'r1509', '豐臺區宋家莊': 'r70132', '石景山區模式口': 'r2882', '石景山區蘋果園': 'r1923', '石景山區古城/八角': 'r1924', '石景山區魯谷': 'r1926', '石景山區石景山其它': 'r1927', '大興區亦莊': 'r5959', '大興區舊宮': 'r5960', '大興區黃村': 'r5961', '大興區西紅門': 'r7043', '大興區龐各莊': 'r70633', '大興區龍湖天街購物中心': 'r85684', '大興區天宮院': 'r89454', '通州區果園': 'r5956', '通州區梨園': 'r5957', '通州區新華大街': 'r5958', '通州區九棵樹': 'r7521', '通州區通州北苑': 'r23045', '通州區武夷花園': 'r23990', '通州區馬駒橋': 'r25907', '通州區次渠': 'r70618', '通州區北關': 'r85513', '通州區土橋': 'r86548', '通州區宋莊': 'r64881', '通州區西集': 'r64882', '通州區物資學院': 'r64883', '昌平區回龍觀': 'r5953', '昌平區天通苑': 'r5954', '昌平區昌平鎮': 'r5955', '昌平區小湯山': 'r7042', '昌平區南口鎮': 'r23042', '昌平區北七家': 'r23043', '昌平區沙河': 'r23044', '昌平區明十三陵': 'r86572', '昌平區居庸關長城': 'r86574', '昌平區十三陵水庫': 'r86575', '房山區良鄉': 'r12011', '房山區仙棲洞': 'r86567', '房山區上方山國家森林公園': 'r86568', '房山區雲居滑雪場': 'r86570', '房山區霞雲嶺國家森林公園': 'r86571', '房山區長陽鎮': 'r30781', '房山區城關鎮': 'r67342', '房山區竇店鎮': 'r67346', '房山區閻村鎮': 'r67349', '房山區燕山': 'r67350', '房山區河北鎮': 'r67374', '房山區十渡鎮': 'r67376', '房山區青龍湖鎮': 'r67384', '順義區國展': 'r12016', '順義區順義': 'r23041', '順義區蓮花山滑雪場': 'r86566', '順義區小湯山/央美博藝藝術館': 'r86569', '順義區後沙峪': 'r64877', '順義區馬坡牛欄山': 'r64878', '順義區南彩': 'r64879', '順義區石園': 'r64880', '延慶區八達嶺鎮': 'r65447', '延慶區大榆樹鎮': 'r65448', '延慶區大莊科鄉': 'r65449', '延慶區井莊鎮': 'r65450', '延慶區舊縣鎮': 'r65451', '延慶區康莊鎮': 'r65452', '延慶區劉斌堡鄉': 'r65453', '延慶區千家店鎮': 'r65454', '延慶區沈家營鎮': 'r65455', '延慶區四海鎮': 'r65456', '延慶區香營鄉': 'r65457', '延慶區延慶鎮': 'r65458', '延慶區永寧鎮': 'r65459', '延慶區張山營鎮': 'r65460', '延慶區珍珠泉鄉': 'r65461', '延慶區延慶區其他': 'r27618', '密雲區北莊鎮': 'r65429', '密雲區不老屯鎮': 'r65430', '密雲區大城子鎮': 'r65431', '密雲區東邵渠鎮': 'r65432', '密雲區馮家峪鎮': 'r65433', '密雲區高嶺鎮': 'r65434', '密雲區古北口鎮': 'r65435', '密雲區河南寨鎮': 'r65436', '密雲區巨各莊鎮': 'r65437', '密雲區經濟開發區': 'r65438', '密雲區密雲鎮': 'r65439', '密雲區穆家峪鎮': 'r65440', '密雲區十里堡鎮': 'r65441', '密雲區石城鎮': 'r65442', '密雲區太師屯鎮': 'r65443', '密雲區西田各莊鎮': 'r65444', '密雲區溪翁莊鎮': 'r65445', '密雲區新城子鎮': 'r65446', '密雲區密雲區其他': 'r27617', '懷柔區懷柔區': 'r27615', '門頭溝區門頭溝區': 'r27614', '平谷區平谷區': 'r27616' } cooks = { '私房菜': 'g1338', '水果生鮮': 'g2714', '食品保健': 'g33759', '下午茶': 'g34014', '人氣餐廳': 'g34032', '早茶': 'g34055', '福建菜': 'g34059', '飲品店': 'g34236', '北京菜': 'g311', '家常菜': 'g1783', '臺灣菜': 'g107', '魯菜': 'g26483', '川菜': 'g102', '俄羅斯菜': 'g1845', '湘菜': 'g104', '湖北菜': 'g246', '雲貴菜': 'g6743', '徽菜': 'g26482', '小龍蝦': 'g219', '本幫江浙菜': 'g101', '粉面館': 'g1817', '粵菜': 'g103', '創意菜': 'g250', '東北菜': 'g106', '新疆菜': 'g3243', '燒烤': 'g508', '西北菜': 'g26481', '素菜': 'g109', '火鍋': 'g110', '江河湖海鮮': 'g251', '小吃快餐': 'g112', '日本菜': 'g113', '韓國料理': 'g114', '東南亞菜': 'g115', '西餐': 'g116', '自助餐': 'g111', '麵包甜點': 'g117', '其他美食': 'g118' }
第二步,得到css屬性和顯示的文字的對應關係。請參考上一篇文章這樣會得到一個字典。每天需要重新獲取,當天可以直接使用。
第三步,使用requests迴圈訪問詳細商區和菜系,提取出所有商家的url,然後從商家的url中提取出資料。每個商區和菜系下可能有很多頁內容,只需要迴圈抓取就行。
為了爬取速度,我選用執行緒池來實現(本來還想用scrapy和aiohttp實現,代理不能用就放棄了),用的是python3自帶的concurrent.futures下的ThreadPoolExecutor。不懂的可以百度,使用非常方便。
測試的時候發現我在免費網站抓取的代理基本都被大眾點評封了(狀態碼為200,但得到的是垃圾資料),看來他們也在抓代理,然後直接封。所以只是得到了幾條資料,不過已經知道爬蟲能使用。另外因為只是學習使用,程式碼並沒有異常處理,有實際需求的可以自己加上代理和異常處理。
獲取商區和菜系程式碼:
# -*- coding: utf-8 -*-
"""
date: Mon Nov 26 14:54:50 2018
python: Anaconda 3.6.5
author: kanade
email: [email protected]
"""
import requests
import pyquery
class GetArea(object):
'''
獲取地區和美食分類
'''
def __init__(self):
doc = self.get_query()
self.areas = self.get_area(doc)
self.cooks = self.get_cook(doc)
def get_query(self):
url = 'http://www.dianping.com/beijing/food'
headers = {
'Host':'www.dianping.com',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36'
}
resp = requests.get(url, headers=headers)
if resp.status_code == 200:
html = resp.text
doc = pyquery.PyQuery(html)
html = doc('script.J_auto-load').html()
doc = pyquery.PyQuery(html)
return doc
def get_area(self, doc):
items = doc('.fpp_business .list').items()
areas = {}
for item in items:
area = item.find('dt a').text()
lis = item.find('li a').items()
for li in lis:
url = li.attr.href
id_ = url.split('/')[-1]
addr = li.text()
addr = area + addr
areas.setdefault(addr,id_)
return areas
def get_cook(self, doc):
items = doc('.fpp_cooking a').items()
cooks = {}
for item in items:
cook = item.text()
url = item.attr.href
_id = url.split('/')[-1]
cooks.setdefault(cook,_id)
return cooks
if __name__ == '__main__':
ga = GetArea()
print(ga.areas)
print(ga.cooks)
還有一些模組程式碼比較長,就不貼了。直接放在github上了。
DianPingSpidergithub地址
如果有什麼反爬機制比較有意思的,還請留言告訴我,非常感謝。