Python爬虫 爬取美团外卖

2,496 阅读7分钟

我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

页面分析

  1. 登录、设置地址、查找目标店铺

  这些步骤都人工操作了,就不需要分析页面了。唯一碰到的问题是,使用PC浏览器登录时,发送验证码的滚动条验证步骤无法通过,需要使用手机浏览器成功发送验证码后,将验证码填入PC端登录

  • 巨坑注意:虽然PC端无法通过滚动条验证步骤,但是该动作还是要做的,否则即使填入了手机获取到的验证码,是无法提交登录的。(与甲方共同测试时,在这个环节卡了好久)
  1. 目标店铺数据分析

  该步骤不难,F12打开chrome的开发者模式,进入店铺后,使用关键词搜索一下,就能发现,所有的菜品,都在一个food的response中

编辑

手机页面是竖屏,把窗口收窄能够有更好的浏览体验

编辑

人工复制数据的步骤

  1. 图片目标分析

  图片的请求则相当简单,只需要设置请求头,再使用GET请求图片网址即可,而图片网址在目标店铺的数据中,每个菜品都有对应的图片网址。

编辑

图片网址请求

解决步骤与代码

  1. 将人工获取的店铺数据存入TXT文件,放在一个目录下,如下图:

编辑

人工获取的数据

  1. 有了数据之后,遍历、读取、加工数据、下载图片就都是基础的json、pandas与requests的操作了,具体详见代码注释吧。

`​

页面分析

  1. 登录、设置地址、查找目标店铺

  这些步骤都人工操作了,就不需要分析页面了。唯一碰到的问题是,使用PC浏览器登录时,发送验证码的滚动条验证步骤无法通过,需要使用手机浏览器成功发送验证码后,将验证码填入PC端登录

  • 巨坑注意:虽然PC端无法通过滚动条验证步骤,但是该动作还是要做的,否则即使填入了手机获取到的验证码,是无法提交登录的。(与甲方共同测试时,在这个环节卡了好久)
  1. 目标店铺数据分析

  该步骤不难,F12打开chrome的开发者模式,进入店铺后,使用关键词搜索一下,就能发现,所有的菜品,都在一个food的response中

编辑

手机页面是竖屏,把窗口收窄能够有更好的浏览体验

编辑

人工复制数据的步骤

  1. 图片目标分析

  图片的请求则相当简单,只需要设置请求头,再使用GET请求图片网址即可,而图片网址在目标店铺的数据中,每个菜品都有对应的图片网址。

编辑

图片网址请求

解决步骤与代码

  1. 将人工获取的店铺数据存入TXT文件,放在一个目录下,如下图:

编辑

人工获取的数据

  1. 有了数据之后,遍历、读取、加工数据、下载图片就都是基础的json、pandas与requests的操作了,具体详见代码注释吧。

页面分析

  1. 登录、设置地址、查找目标店铺

  这些步骤都人工操作了,就不需要分析页面了。唯一碰到的问题是,使用PC浏览器登录时,发送验证码的滚动条验证步骤无法通过,需要使用手机浏览器成功发送验证码后,将验证码填入PC端登录

  • 巨坑注意:虽然PC端无法通过滚动条验证步骤,但是该动作还是要做的,否则即使填入了手机获取到的验证码,是无法提交登录的。(与甲方共同测试时,在这个环节卡了好久)
  1. 目标店铺数据分析

  该步骤不难,F12打开chrome的开发者模式,进入店铺后,使用关键词搜索一下,就能发现,所有的菜品,都在一个food的response中

编辑

手机页面是竖屏,把窗口收窄能够有更好的浏览体验

编辑

人工复制数据的步骤

  1. 图片目标分析

  图片的请求则相当简单,只需要设置请求头,再使用GET请求图片网址即可,而图片网址在目标店铺的数据中,每个菜品都有对应的图片网址。

编辑

图片网址请求

解决步骤与代码

  1. 将人工获取的店铺数据存入TXT文件,放在一个目录下,如下图:

编辑

人工获取的数据

  1. 有了数据之后,遍历、读取、加工数据、下载图片就都是基础的json、pandas与requests的操作了,具体详见代码注释吧。

    @classmethod     def parse_data(cls, filename: pl.Path) -> tuple:         """         解析获取到的美团店铺数据         :param filename: 存储数据的文件路径         :return:         """         with open(filename, 'r', encoding='utf-8') as fin:             data = fin.read()         data = json.loads(data)         # 解析数据步骤         shop_name = data['data']['poi_info']['name']         data = data['data']['food_spu_tags']         df = pd.DataFrame()         for tag in data:             dfx = pd.DataFrame(tag['spus'])             dfx['分类'] = tag['name']             df = pd.concat([df, dfx])         df = df.loc[df['分类'].map(lambda x: x not in ['折扣', '热销', '推荐'])]         df['原价'] = df.apply(cls.get_origin_price, axis=1)         df.reset_index(inplace=True, drop=True)         return shop_name, df

    @classmethod     def download_picture(cls, url: str, filename: pl.Path):         """         下载图片的方法         :param url: 图片的地址         :param filename: 输出图片的路径(含文件名)         :return:         """         # 初始化请求头         headers = {             "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/,/*;q=0.8",             "accept-encoding": "gzip, deflate, br",             "accept-language": "zh-CN,zh;q=0.9",             "referer": "h5.waimai.meituan.com/",             "sec-ch-ua": "" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"",             "sec-ch-ua-mobile": "?0",             "sec-ch-ua-platform": ""Windows"",             "sec-fetch-dest": "image",             "sec-fetch-mode": "no-cors",             "sec-fetch-site": "cross-site",             "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"         }         # 下载文件         file = requests.get(url, headers, stream=True)         with open(filename, "wb") as code:             for chunk in file.iter_content(chunk_size=1024):  # 边下载边存硬盘                 if chunk:                     code.write(chunk)

    @classmethod     def get_pictures(cls, shop_name: str, data: pd.DataFrame, pic_dir: pl.Path):         """         批量获取图片数据的方法         :param shop_name: 店铺名         :param data: 数据         :param pic_dir: 图片存放的目录         :return:         """         print(f'开始下载店铺:{shop_name} 的图片')         # 下载前按照菜品名称与图片地址进行去重处理,减少请求数量         download_data = data.copy()         download_data = download_data.drop_duplicates(['name', 'picture'], keep='last')         # 筛选去除图片地址为空的         download_data = download_data.loc[             (download_data['picture'].map(lambda x: pd.notnull(x))) |             (download_data['picture'] != '')             ]         max_len = len(download_data)         for idx, food in enumerate(download_data.to_dict(orient='records')):  # 遍历数据             pic_url = food['picture']             # 拆分获取图片扩展名             suffix = pl.Path(pic_url.split('/')[-1]).suffix             # 加工出图片的路径(包含名称)             name = food['name'].replace('', '').replace('/', '')             filename = pic_dir / f"{name}{suffix}"             # 使用下载方法下载             try:                 cls.download_picture(pic_url, filename)                 print(f'({idx+1}/{max_len})菜品:{food["name"]} 图片下载完成')             except Exception as e:                 print(f'!!!({idx+1}/{max_len})菜品:{food["name"]} 图片下载失败,错误提示是: {e}')             # 随机暂停             time.sleep(randint(1, 3) / 10)

    @classmethod     def write_data(cls, shop_name, data, shop_dir):         """         将数据输出至excel文件         :param shop_name: 店铺名         :param data: 数据         :param shop_dir: 店铺存放的文件夹         :return:         """         data.to_excel(shop_dir / f'{shop_name}.xlsx', index=False)

    def run(self):         """         运行程序         :return:         """         try:             for filename in self.file_path.iterdir():                 # 先解析人工取得的数据                 shop_name, data = self.parse_data(filename)                 # 再创建文件夹                 shop_dir, pic_dir = self.create_dir(shop_name)                 # 写入Excel文件                 self.write_data(shop_name, data, shop_dir)                 # 获取图片                 self.get_pictures(shop_name, data, pic_dir)                 print(f'店铺:{shop_name}的数据已解析下载完毕,数据存储在:“{shop_dir.absolute()}”路径下')             return True, None         except Exception as e:             return False, e

if name == 'main':     spider = SpiderObj()     res, err = spider.run()     if res:         input('程序已运行完毕,按回车键退出')     else:         input(f'程序运行出错,错误提示是: {err}')


![](<> "点击并拖拽以移动")

1.  ## 程序运行

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bcd9577a8d77419fa4b555955855db7a~tplv-k3u1fbpfcp-zoom-1.image)​

![](<> "点击并拖拽以移动")编辑

大功告成

## **总结**

  不管全自动还是半自动,能解决问题的都是好爬虫。最后实现的爬虫实际上使用到的知识点都不是困难的:

1.  读取文件,使用json、pandas解析数据输出Excel表格;
1.  使用for循环,requests的get请求下载图片
1.  创建文件夹,去重、try-except等

至于回避的那三个难点留给某位大老板用Money激发我去攻克吧^_^

##                                      我是政胤 期待你的关注   

​
大功告成

## **总结**

  不管全自动还是半自动,能解决问题的都是好爬虫。最后实现的爬虫实际上使用到的知识点都不是困难的:

1.  读取文件,使用json、pandas解析数据输出Excel表格;
1.  使用for循环,requests的get请求下载图片
1.  创建文件夹,去重、try-except等

至于回避的那三个难点留给某位大老板用Money激发我去攻克吧^_^

##                                      我是政胤 期待你的关注   

​

大功告成

## **总结**

  不管全自动还是半自动,能解决问题的都是好爬虫。最后实现的爬虫实际上使用到的知识点都不是困难的:

1.  读取文件,使用json、pandas解析数据输出Excel表格;
1.  使用for循环,requests的get请求下载图片
1.  创建文件夹,去重、try-except等

至于回避的那三个难点留给某位大老板用Money激发我去攻克吧^_^

##                                      我是政胤 期待你的关注