1. 程式人生 > >大眾點評之執行緒池實現全站爬取

大眾點評之執行緒池實現全站爬取

要想全站爬取,首先需要分商區、菜系,這樣得到的資料才全,不然網站預設只顯示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地址

如果有什麼反爬機制比較有意思的,還請留言告訴我,非常感謝。