From 2a0c136deb98626dd1f724ae2e2fb574ea5f3cc2 Mon Sep 17 00:00:00 2001 From: pf3x5aziv <470574122@qq.com> Date: Sun, 17 Apr 2022 15:55:39 +0800 Subject: [PATCH] Update README.md --- README.md | 436 +++++++++++++++++++++++++++--------------------------- 1 file changed, 221 insertions(+), 215 deletions(-) diff --git a/README.md b/README.md index 8334dc1..8444bf1 100644 --- a/README.md +++ b/README.md @@ -1,215 +1,221 @@ -# 京东显卡商品信息分布式爬虫 - -### 介绍 - -本爬虫分两个部分: - -- 第一部分搜索页爬虫是使用selenium爬取搜索页, 主要获取的是商品的sku和价格 - -- 第二部分商品页爬虫使用scrapy爬取详情页的商品介绍信息, 主要获取商品的型号 - -由于京东商品种类繁多, 不同的商品品类间差异巨大, 商品页爬虫的代码也就不近相同. 出于成本和时间考虑, 搜索页爬虫使用**3080**作为搜索关键词, 商品页爬虫爬取搜索页爬虫获取的商品对应的详情页 - -其整体架构如下: - -![](D:\python_class\jd-distributed-crawler\img\2022-04-15-16-28-10-image.png) - -### 电商网站筛选 - -爬虫的目的就是为了获取一定量的有价值的信息, 就目前中文互联网来说, 电商网站是信息密度和价值密度比较高的地方, 因此使用爬虫获取信息有一定的可用性. - -目前而言, 淘宝获取的商品信息结果层次最多, 品类最全. 但是首先淘宝商品搜索页混杂天猫,天猫国际, 淘宝卖家等各类各不相同的店铺, 因此其商品详情页结构也大相径庭; 其次是淘宝历史包袱比较重, 因此有许多结构混乱的页面混杂其中, 难以提取数据. - -因此, 实际考虑后确认, 淘宝不是太适合新人小团队去学习试炼爬虫爬取信息. - -相对而言, 京东搜索页基本同一为京东自己的页面, 京东自营, 旗舰店和商家店结构基本没有区别, 详情页结构也一致, 对爬虫比较友好(京东唯一不太友好的地方只有, 其主站页面下并没有写robots.txt). 因此考虑爬取京东. - -### 搜索页爬虫 - -主要由`settings.yaml`和`jdSearchSeleniumSpider.py`两部分组成,`settings.yaml`是配置文件, `jdSearchSeleniumSpider.py`是代码执行主体 - -#### 前期工作 - -基本而言, 任何有搜索功能的网站都会对其搜索功能做一定的限制, 以防止单个ip过高频率的访问, 占用过多资源. - -京东也不例外, 对于未登录的请求, 甚至是带着header和cookie的请求, 往往都是秉持拒绝的态度, 要求验证登录: - -![](D:\python_class\jd-distributed-crawler\img\2022-04-15-13-25-48-image.png) - -![](D:\python_class\jd-distributed-crawler\img\2022-04-15-13-26-42-image.png) - -搜索引擎的爬虫验证一般级别都比较高, 难以绕过. 考虑到搜索页的数量相对于商品页的数量往往是少一个数量级, 且京东单个搜索词的页面量也大致在几十几百页的数量级之间, 因此使用python的`selenium`库, 驱动浏览器去自动化访问搜索页, 进而获取数据, 是比较简单且可行的方案. - -#### settings.yaml - -`setting.yaml`是搜索页爬虫的配置文件, 其内容如下: - -```yaml -search: - keyword: '3080' - sleep: 5 - -redis: - host: '120.24.87.40' - port: '6379' - password: 'Guet@207' - db: 1 - -mongodb: - host: '120.24.87.40' - port: '27017' - username: "admin" - password: "123456" - -result: - rpush_key: "jd:start_urls" -``` - -- `search` - - - `keyword`: 要搜索的关键字, 即在京东商品搜索上的要搜索的商品; - - - `sleep`: 休眠时间, 单位: 秒; 由于**selenium**操作页面的动作(例如点击翻页, 下拉)是**异步**的, 而读取页面是**同步**的. 京东需要执行下拉这个操作后才加载出所有的商品信息, 为了读取全当前的页面信息, 需要休眠一段时间等待异步操作完成; - -- `redis` - - - `host`: redis部署的服务器地址; - - - `port`: redis部署时开放的端口号; - - - `password`: redis部署时设置的auth; - - - `db`: 需要使用redis的第几个数据库 - -- `mongodb` - - - `host`: MongoDB部署的服务器地址; - - - `port`: MongoDB部署时开放的端口号; - - - `username`: MongoDB部署时设置的auth的用户名, 或者说要使用的MongoDB的数据库名; - - - `password`: MongoDB部署时设置的auth. - -- `result` - - - `rpush_key`: 爬虫获取到的数据形成的url要放入redis时设置的key, url在Redis中存储方式是列表 - -#### jdSearchSeleniumSpider.py - -搜索页爬虫的程序入口, 根据京东搜索页面爬取页面元素. - -启动程序后进入京东的商品搜索页面. 搜索页的搜索关键字为`settings.py`中设定的`search.keyword`. - -京东的搜索页面会分两个阶段加载, 第一个加载阶段加载成功后(即加载出30个商品的信息), 爬虫会执行`scroll_by_xpath()`, 作用是滚动网页到网页底部, 触发京东搜索页面的第二个加载阶段, 把一个页面上的信息都加载到浏览器里. - -`scroll_by_xpath()` 内部原理就是驱动浏览器移动视图, 使视图可以看到指定的元素. 在该爬虫中, 指定的网页元素的**xpath**为`//*[@id="J_bottomPage"]/span[1]/a[9]`, 代表的是京东搜索页面的下一页按钮这一网页元素. - -两个阶段都加载完成后, 获取网页中所有商品的sku和价格. - -![](D:\python_class\jd-distributed-crawler\img\2022-04-15-18-40-23-image.png) - -sku是京东给商品的编号, 且可以根据sku拼接对应商品的详情页的URL. - -获取到sku和价格后, 将sku拼接为URL, rpush到Redis设置好的数据库的`result.rpush_key`中, 并且将每个商品的数据以`{'sku': sku, 'price': price}`的形式, 放入MongoDB设置好的数据库中. 放入MongoDB前会根据sku字段查询数据是否存在(原子操作, 可分布式), 防止重复插入. - -### 商品页爬虫 - -商品页爬虫使用了`scrapy_redis`去写, 即`Scrapy`的redis版本, 他与`Scrapy`的主要区别就是把存放URL于Reids数据库中, 利用Redis的特性, 即在多个请求请求同一个数据时, 会默认对请求排序, 有一个队列效果, 保证了分布式爬虫获取到不同的URL. - -`scrapy_redis`框架下的爬虫执行是异步的, 即, 从Redis数据库的列表中获取URL后, 发起Request, 便可以执行下一次从Redis中获取Redis, 而发起Request得到的response则等得到后再由回调函数处理(即执行`jdsku.py`里的`parse`方法). - -该爬虫只需要在**本爬虫目录下**, 调用`scrapy crawl jdsku`即可启动. - -#### settings.py - -配置文件, 具体配置查看`Scrapy`的配置, 其中有几个是`scrapy_redis`的配置或本次项目引入的配置, 在此说明: - -个人配置: - -`PROXY_SERVER_URL`: IP代理商提供的直连IP的API地址; - -`TEST_PAGE`: 用于测试IP代理商提供的IP代理服务是否能访问JD的测试地址, 可以按需更换不同的京东商品地址; - -`mongodb`: 配置时需要以字典形式配置 - -- `host`: MongoDB服务器的地址; - -- `port`: MongoDB服务器暴露的端口; - -- `username`: MongoDB要使用的用户名(也是数据库名) - -- `password`: MongoDB对应用户名的验证密码 - -`cookies`: 浏览器访问京东时的`cookies`, 有助于模拟浏览器访问, 降低失败率 - -scrapy_redis配置: - -`DONT_FILTER`: Redis访问不去重, True代表不去重. 不去重的原因是使用代理访问商品详情页时可能会失败, 此时会将URL重新放回Redis的列表开头; - -`SCHEDULER='scrapy_redis.scheduler.Scheduler'`: 使用scrapy_redis中的调度器, 即保证每台主机爬取的URL地址都不同的Scheduler. - -`DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'`: 配置scrapy使用的去重类, 即RFPDupeFilter. - - `SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"`: 使用`scrapy_redis.picklecompat`作为序列化, 不序列化的数据可能会出现乱码. - -   `AUTOTHROTTLE_ENABLED = True`: 开启自动限速, 控制爬取速度,降低对方服务器压力. - -#### items.py - -代码如下: - -```python -import scrapy - - -class JdskuspiderItem(scrapy.Item): - # sku-id - sku = scrapy.Field() - # title - title = scrapy.Field() - # 显卡型号 - model = scrapy.Field() - # 显存类型 - memoryType = scrapy.Field() - # 显存位宽 - bitWidth = scrapy.Field() - # 显存容量 - memoryCapacity = scrapy.Field() -``` - -item是爬虫数据的容器, 方便我们按对应字段存储数据 - -#### jdsku.py - -商品页爬虫的程序入口, 根据redis数据库对应的列表访问对应URL, 在京东商品详情页面爬取页面元素. 因为使用`scrapy_redis`框架, 也就是`Scrapy`的Redis分布式版本, 因此大部分代码都是在重写方法. - -对于`class JdskuSpider(RedisSpider)`: - -**属性**: - -`name`: 爬虫的名字, 后续在终端启动爬虫时需要输入的爬虫名; - -`allowed_domains`: 运行爬取的URL范围; - -`redis_key`: Redis数据库中存放要爬取的URL的列表的Key名, 会获取列表中的第一个数据; - -`redis_connection`: 存放一个新的Redis连接, 该连接后续用于访问失败时重新把URL返回给Redis列表; - -`mongo_connection`: 存放一个新的MongoDB连接, 该连接后续用于存放最终数据. - -**方法**: - -两个方法都是重写方法, 方法名和参数都固定, 两个方法都是自动调用. - -`make_requests_from_url(self, url)`: - -- `scrapy_redis`框架会调用该方法, 发起一个访问参数里URL的request, 返回一个response, 框架会将response发给他的回调函数处理(即下述的`parse`); - -- `meta={'url':url}`表示会将`{'url': url}`作为数据传递下去, 这样在他的回调函数(`parse`)里就能使用到该数据. - -`parse(self, response)`: - -- `scrapy_redis`发送的请求得到response后的回调函数; - -- 作用是解析商品详情页(针对特定商品), 提取数据, 并将其保存到MongoDB中, 若提取数据表现出来的是被京东拦截, 那么把该次访问的URL重新返回到Redis列表开头. +# 京东显卡商品信息分布式爬虫 + +### 介绍 + +本爬虫分两个部分: + +- 第一部分搜索页爬虫是使用selenium爬取搜索页, 主要获取的是商品的sku和价格 + +- 第二部分商品页爬虫使用scrapy爬取详情页的商品介绍信息, 主要获取商品的型号 + +由于京东商品种类繁多, 不同的商品品类间差异巨大, 商品页爬虫的代码也就不近相同. 出于成本和时间考虑, 搜索页爬虫使用**3080**作为搜索关键词, 商品页爬虫爬取搜索页爬虫获取的商品对应的详情页 + +其整体架构如下: + +![](D:\python_class\jd-distributed-crawler\img\2022-04-15-16-28-10-image.png) + +### 电商网站筛选 + +爬虫的目的就是为了获取一定量的有价值的信息, 就目前中文互联网来说, 电商网站是信息密度和价值密度比较高的地方, 因此使用爬虫获取信息有一定的可用性. + +目前而言, 淘宝获取的商品信息结果层次最多, 品类最全. 但是首先淘宝商品搜索页混杂天猫,天猫国际, 淘宝卖家等各类各不相同的店铺, 因此其商品详情页结构也大相径庭; 其次是淘宝历史包袱比较重, 因此有许多结构混乱的页面混杂其中, 难以提取数据. + +因此, 实际考虑后确认, 淘宝不是太适合新人小团队去学习试炼爬虫爬取信息. + +相对而言, 京东搜索页基本同一为京东自己的页面, 京东自营, 旗舰店和商家店结构基本没有区别, 详情页结构也一致, 对爬虫比较友好(京东唯一不太友好的地方只有, 其主站页面下并没有写robots.txt). 因此考虑爬取京东. + +### 搜索页爬虫 + +主要由`settings.yaml`和`jdSearchSeleniumSpider.py`两部分组成,`settings.yaml`是配置文件, `jdSearchSeleniumSpider.py`是代码执行主体 + +#### 前期工作 + +基本而言, 任何有搜索功能的网站都会对其搜索功能做一定的限制, 以防止单个ip过高频率的访问, 占用过多资源. + +京东也不例外, 对于未登录的请求, 甚至是带着header和cookie的请求, 往往都是秉持拒绝的态度, 要求验证登录: + +![](D:\python_class\jd-distributed-crawler\img\2022-04-15-13-25-48-image.png) + +![](D:\python_class\jd-distributed-crawler\img\2022-04-15-13-26-42-image.png) + +搜索引擎的爬虫验证一般级别都比较高, 难以绕过. 考虑到搜索页的数量相对于商品页的数量往往是少一个数量级, 且京东单个搜索词的页面量也大致在几十几百页的数量级之间, 因此使用python的`selenium`库, 驱动浏览器去自动化访问搜索页, 进而获取数据, 是比较简单且可行的方案. + +#### settings.yaml + +`setting.yaml`是搜索页爬虫的配置文件, 其内容如下: + +```yaml +search: + keyword: '3080' + sleep: 5 + +redis: + host: '120.24.87.40' + port: '6379' + password: 'Guet@207' + db: 1 + +mongodb: + host: '120.24.87.40' + port: '27017' + username: "admin" + password: "123456" + +result: + rpush_key: "jd:start_urls" +``` + +- `search` + + - `keyword`: 要搜索的关键字, 即在京东商品搜索上的要搜索的商品; + + - `sleep`: 休眠时间, 单位: 秒; 由于**selenium**操作页面的动作(例如点击翻页, 下拉)是**异步**的, 而读取页面是**同步**的. 京东需要执行下拉这个操作后才加载出所有的商品信息, 为了读取全当前的页面信息, 需要休眠一段时间等待异步操作完成; + +- `redis` + + - `host`: redis部署的服务器地址; + + - `port`: redis部署时开放的端口号; + + - `password`: redis部署时设置的auth; + + - `db`: 需要使用redis的第几个数据库 + +- `mongodb` + + - `host`: MongoDB部署的服务器地址; + + - `port`: MongoDB部署时开放的端口号; + + - `username`: MongoDB部署时设置的auth的用户名, 或者说要使用的MongoDB的数据库名; + + - `password`: MongoDB部署时设置的auth. + +- `result` + + - `rpush_key`: 爬虫获取到的数据形成的url要放入redis时设置的key, url在Redis中存储方式是列表 + +#### jdSearchSeleniumSpider.py + +搜索页爬虫的程序入口, 根据京东搜索页面爬取页面元素. + +启动程序后进入京东的商品搜索页面. 搜索页的搜索关键字为`settings.py`中设定的`search.keyword`. + +京东的搜索页面会分两个阶段加载, 第一个加载阶段加载成功后(即加载出30个商品的信息), 爬虫会执行`scroll_by_xpath()`, 作用是滚动网页到网页底部, 触发京东搜索页面的第二个加载阶段, 把一个页面上的信息都加载到浏览器里. + +`scroll_by_xpath()` 内部原理就是驱动浏览器移动视图, 使视图可以看到指定的元素. 在该爬虫中, 指定的网页元素的**xpath**为`//*[@id="J_bottomPage"]/span[1]/a[9]`, 代表的是京东搜索页面的下一页按钮这一网页元素. + +两个阶段都加载完成后, 获取网页中所有商品的sku和价格. + +![](D:\python_class\jd-distributed-crawler\img\2022-04-15-18-40-23-image.png) + +sku是京东给商品的编号, 且可以根据sku拼接对应商品的详情页的URL. + +获取到sku和价格后, 将sku拼接为URL, rpush到Redis设置好的数据库的`result.rpush_key`中, 并且将每个商品的数据以`{'sku': sku, 'price': price}`的形式, 放入MongoDB设置好的数据库中. 放入MongoDB前会根据sku字段查询数据是否存在(原子操作, 可分布式), 防止重复插入. + +### 商品页爬虫 + +商品页爬虫使用了`scrapy_redis`去写, 即`Scrapy`的redis版本, 他与`Scrapy`的主要区别就是把存放URL于Reids数据库中, 利用Redis的特性, 即在多个请求请求同一个数据时, 会默认对请求排序, 有一个队列效果, 保证了分布式爬虫获取到不同的URL. + +`scrapy_redis`框架下的爬虫执行是异步的, 即, 从Redis数据库的列表中获取URL后, 发起Request, 便可以执行下一次从Redis中获取Redis, 而发起Request得到的response则等得到后再由回调函数处理(即执行`jdsku.py`里的`parse`方法). + +该爬虫只需要在**本爬虫目录下**, 调用`scrapy crawl jdsku`即可启动. + +#### settings.py + +配置文件, 具体配置查看`Scrapy`的配置, 其中有几个是`scrapy_redis`的配置或本次项目引入的配置, 在此说明: + + + +个人配置: + +`PROXY_SERVER_URL`: IP代理商提供的直连IP的API地址; + +`TEST_PAGE`: 用于测试IP代理商提供的IP代理服务是否能访问JD的测试地址, 可以按需更换不同的京东商品地址; + +`mongodb`: 配置时需要以字典形式配置 + +- `host`: MongoDB服务器的地址; + +- `port`: MongoDB服务器暴露的端口; + +- `username`: MongoDB要使用的用户名(也是数据库名) + +- `password`: MongoDB对应用户名的验证密码 + +`cookies`: 浏览器访问京东时的`cookies`, 有助于模拟浏览器访问, 降低失败率 + + + +scrapy_redis配置: + +`DONT_FILTER`: Redis访问不去重, True代表不去重. 不去重的原因是使用代理访问商品详情页时可能会失败, 此时会将URL重新放回Redis的列表开头; + +`SCHEDULER='scrapy_redis.scheduler.Scheduler'`: 使用scrapy_redis中的调度器, 即保证每台主机爬取的URL地址都不同的Scheduler. + +`DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'`: 配置scrapy使用的去重类, 即RFPDupeFilter. + + `SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"`: 使用`scrapy_redis.picklecompat`作为序列化, 不序列化的数据可能会出现乱码. + +   `AUTOTHROTTLE_ENABLED = True`: 开启自动限速, 控制爬取速度,降低对方服务器压力. + +#### items.py + +代码如下: + +```python +import scrapy + + +class JdskuspiderItem(scrapy.Item): + # sku-id + sku = scrapy.Field() + # title + title = scrapy.Field() + # 显卡型号 + model = scrapy.Field() + # 显存类型 + memoryType = scrapy.Field() + # 显存位宽 + bitWidth = scrapy.Field() + # 显存容量 + memoryCapacity = scrapy.Field() +``` + +item是爬虫数据的容器, 方便我们按对应字段存储数据 + + + +#### jdsku.py + +商品页爬虫的程序入口, 根据redis数据库对应的列表访问对应URL, 在京东商品详情页面爬取页面元素. 因为使用`scrapy_redis`框架, 也就是`Scrapy`的Redis分布式版本, 因此大部分代码都是在重写方法. + +对于`class JdskuSpider(RedisSpider)`: + +**属性**: + +`name`: 爬虫的名字, 后续在终端启动爬虫时需要输入的爬虫名; + +`allowed_domains`: 运行爬取的URL范围; + +`redis_key`: Redis数据库中存放要爬取的URL的列表的Key名, 会获取列表中的第一个数据; + +`redis_connection`: 存放一个新的Redis连接, 该连接后续用于访问失败时重新把URL返回给Redis列表; + +`mongo_connection`: 存放一个新的MongoDB连接, 该连接后续用于存放最终数据. + +**方法**: + +两个方法都是重写方法, 方法名和参数都固定, 两个方法都是自动调用. + +`make_requests_from_url(self, url)`: + +- `scrapy_redis`框架会调用该方法, 发起一个访问参数里URL的request, 返回一个response, 框架会将response发给他的回调函数处理(即下述的`parse`); + +- `meta={'url':url}`表示会将`{'url': url}`作为数据传递下去, 这样在他的回调函数(`parse`)里就能使用到该数据. + +`parse(self, response)`: + +- `scrapy_redis`发送的请求得到response后的回调函数; + +- 作用是解析商品详情页(针对特定商品), 提取数据, 并将其保存到MongoDB中, 若提取数据表现出来的是被京东拦截, 那么把该次访问的URL重新返回到Redis列表开头.