You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
9.5 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

# 京东显卡商品信息分布式爬虫
### 介绍
本爬虫分两个部分:
- 第一部分搜索页爬虫是使用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列表开头.