欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

大众点评之线程池实现全站爬取

程序员文章站 2022-03-02 19:38:13
...

要想全站爬取,首先需要分商区、菜系,这样得到的数据才全,不然网站默认只显示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
这样只要得到后面的那个标识,然后拼接就行。

得到了这样的字典:

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。不懂的可以百度,使用非常方便。

测试的时候发现我在免费网站抓取的代理基本都被大众点评封了,看来他们也在抓代理,然后直接封。所以只是得到了几条数据,不过已经知道爬虫能使用。另外因为只是学习使用,代码并没有异常处理,有实际需求的可以自己加上代理和异常处理。

获取商区和菜系代码:

# -*- 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地址

如果有什么反爬机制比较有意思的,还请留言告诉我,非常感谢。