实战 Python 网络爬虫:美团美食商家信息和用户评论
一、网站分析及项目设计
美食是人类的毕生追求,说到美食,我们总会想起美团美食,面对类型众多的商家,应如何选择优质的商家,使消费最大合理化。在本 Chat 里,将讲述如何爬取美团商家信息。
废话不多说,我们直接在浏览器打开美团美食的网址,然后打开谷歌的开发者工具,并刷新网页,重新捕捉请求资源,如图所示:
根据店名在 Network 选项卡的各个分类标签下查找数据所在的 HTML 源码位置,在每个请求信息的 Response 下使用 Ctrl+F 快速查找店名(初漾台味黑糖),最终在 Doc 标签下找到相关信息,如图所示:
从图上的信息可以看到,地址栏的 gz 代表区域“广州”,全国的美食商家是以城市进行划分的。而商家的数据是以 JSON 格式表示,网页上显示的信息都可以在此找到。在这个网页中,我们是要查找这个商家的 URL 地址,从而进入商家详细页。
但从美团美食的首页中,我们能获取的信息就这么多,因此,我们先访问店家详细页,发现商家详细页的 URL 地址带有一串数字。不同的商家,数字内容都不一样,如图所示:
通过对比发现,每个商家详细页的 URL 地址只有末端的数字串是不相同的,这应该是美团给商家标记的 id,我们取其中一个商家 id 回到美团首页查找,发现可找到相关信息,如图所示:
根据上述分析,我们可以在美团美食首页里获取商家 id,通过 id 来构建商家详细页的 URL 地址。
得到了商家详细页的 URL 地址后,下一步是在商家详细页里进行数据爬取。数据爬取分为两部分:商家信息和顾客评论,如图所示:
首先,我们找出商家信息所在的请求信息,在开发者工具的 Network 选项卡的 doc 标签下找到相关信息,商家信息是在 doc 标签下找到,并且也是以 JSON 格式表示,如图所示:
接着是分析顾客评论所在的请求信息,最终在 XHR 标签下找到相关的请求信息,如图所示:
综合上述,我们需要从三个请求信息里获取数据,三个请求信息的说明如下:
- 美团美食的首页地址,获取每个商家的 id
- 商家详细页地址,获取商家信息
- 顾客评论的 AJAX 接口,获取顾客评论信息
目前只是简单分析了三个请求信息,每个请求的请求参数和请求头会在功能实现的过程中详细讲解。
根据现有的分析,我们创建项目文件夹 meituan,项目文件分别有 Insql.py、meishi.conf 和 meishi.py,文件说明如下:
- Insql.py 是将数据入库处理,由 ORM 框架 SQLAlchemy 实现
- meishi.conf 设置区域信息,如广州(gz)或北京(bj)等,从而控制爬虫的爬取方向
- meishi.py 实现爬虫功能
二、爬取所有商家信息
简单分析网页后,接下来我们先实现所有商家的信息爬取。由于商家详细页只需要商家 id 即可,因此爬取所有商家信息只需爬取商家 id 即可。
从美团美食的首页得知,其 URL 地址的“gz”代表广州。首页的底部设有分页功能,当点击第二页的时候,URL 末端新增下级目录 pn2,第三页的下级目录为 pn3,以此类推,新增的下级目录代表分页的页数。当遍历 32 次即可得到当前城市所有美食商家信息,如图所示:
根据 URL 的变化规律和商家信息的 HTML 源码结构,所有商家信息的爬取功能定义为函数 get_all(),函数参数 city_list 代表各个城市信息并以字符串表示,如 gz、bj 等,函数代码如图所示:
函数 get_all() 所实现的功能说明如下:
- 首先将参数 city_list 以英文逗号进行截取,得到各个城市的拼音缩写。
- 然后遍历各个城市,分别爬取各个城市的美食信息,每个城市只显示 32 页的美食信息,因此需要遍历 32 次。
- 每次遍历都会对当前分页发送 HTTP 请求,请求头设有 Upgrade-Insecure-Requests、Host 和 Referer 属性,这些属性最好写入请求头,这样可以避开反爬虫检测。特别是 Host 属性,因为 URL 的域名设有城市信息,如 gz.meituan.com,而 Host 属性是为 URL 指定相应的域名,使其一一对应。
- 从当前请求中获取响应内容,并用正则表达式提取当前分页所有的商家 id(即find_poiId)以及访客信息 find_uuid。
- 调用函数 get_info(),将爬取的数据作为函数参数传入。函数 get_info() 是进入商家详细页,爬取商家的基本信息。
三、分别爬取每个商家的信息和用户评论信息
在函数 get_all() 里,我们调用了函数 get_info(),它是进入访问商家详细页的,主要爬取商家的基本信息。商家详情页的 URL 地址为 http://www.meituan.com/meishi/%s/,其中 %s 代表商家 id。
注意:如果对商家详细页发送 HTTP 请求,这里涉及了一个反爬虫机制——Cookies 的使用,我们查看该请求的请求头内容。如图所示:
商家详细页的请求头与一般的请求头并无太大差异,按照以往的开发模式,首先构架 URL 地址,然后对 URL 发送请求,最后从请求里获取响应内容并提取目标数据。按照该思路,商家的基本信息爬取功能如图所示:
当运行程序的时候,程序是没有提取到商家信息了,这说明该请求的响应内容不是商家详细页的网页内容,肯定遇到反爬虫检测。在请求头里,除了尚未加入 Cookies 之外,其余属性已添加,因此,我们尝试加入 Cookies,发现可以提取到商家信息。
但是只使用一个 Cookies 也会中断爬取过程,原因在于访问频繁。为了降低访问频繁,引入 Cookies 池,将代码的请求部分进行修改,如下所示:
从函数 get_info() 里可到,它调用了函数 get_comment(),并将商家 ID 和 find_uuid 分别传入,find_uuid 是从函数 get_all()提取出来的数据,这两个函数参数都是构建顾客评论的 AJAX 接口的请求参数,如图所示:
- 请求参数 uuid 是函数参数 find_uuid
- 请求参数 originUrl 是商家详细页的 URL 地址
- 请求参数 id 是商家 id
因此,函数 get_comment() 的代码如图所示:
四、ORM 框架实现数据持久化存储
爬虫的核心功能大致已实现,接着讲解数据存储方面。数据存储以 MySQL 为例,存储过程使用 ORM 框架 SQLAlchemy,这样可实现数据持久化,它的优点此处不一一讲述。
首先在 Insql.py 里导入 ORM 框架 SQLAlchemy,并创建相关对象,通过 SQLAlchemy 连接本地 MySQL 数据库。数据库编码设为 UTF8mb4,因为评论信息里可能出现手机表情这类特殊内容,这些特殊内容是超出 UTF-8 的编码范围。代码如下:
将商家信息和顾客评论信息分别存储在数据表 meituan_shop 和 meituan_comment。数据表之间存在一对多的数据关系,一个商家会有多条顾客评论,映射类的定义如下:
上述只是定义映射类,数据存储的功能尚未实现。数据存储由函数 shop_db() 和函数 comment_db() 实现,两者会对待存储的数据进行判断,如果数据已存在数据库,则进行更新处理,反之新增一条数据。代码如下:
五、设置配置文件,动态控制爬取方向
配置文件给爬虫程序 meishi.py 读取,用于控制爬虫的爬取方向,比如爬取北京、上海等城市的美食信息。将配置文件命名为 meishi.conf,配置信息如下:
配置文件只设置配置属性 city,属性值是将每个城市编号以英文逗号的形式拼接起来,城市编号是首个字母拼音开头组成的。函数 get_all() 的函数参数 city_list 就是配置属性 city。
在爬虫文件 meishi.py 里,文件运行函数 _main_ 主要读取配置文件的配置属性 city,并调用函数 get_all(),代码如下: