|
|
# 京东显卡商品信息分布式爬虫
|
|
|
|
|
|
### 介绍
|
|
|
|
|
|
本爬虫分两个部分:
|
|
|
|
|
|
- 第一部分搜索页爬虫是使用selenium爬取搜索页, 主要获取的是商品的sku和价格
|
|
|
|
|
|
- 第二部分商品页爬虫使用scrapy爬取详情页的商品介绍信息, 主要获取商品的型号
|
|
|
|
|
|
由于京东商品种类繁多, 不同的商品品类间差异巨大, 商品页爬虫的代码也就不近相同. 出于成本和时间考虑, 搜索页爬虫使用**3080**作为搜索关键词, 商品页爬虫爬取搜索页爬虫获取的商品对应的详情页
|
|
|
|
|
|
其整体架构如下:
|
|
|
|
|
|

|
|
|
|
|
|
### 电商网站筛选
|
|
|
|
|
|
爬虫的目的就是为了获取一定量的有价值的信息, 就目前中文互联网来说, 电商网站是信息密度和价值密度比较高的地方, 因此使用爬虫获取信息有一定的可用性.
|
|
|
|
|
|
目前而言, 淘宝获取的商品信息结果层次最多, 品类最全. 但是首先淘宝商品搜索页混杂天猫,天猫国际, 淘宝卖家等各类各不相同的店铺, 因此其商品详情页结构也大相径庭; 其次是淘宝历史包袱比较重, 因此有许多结构混乱的页面混杂其中, 难以提取数据.
|
|
|
|
|
|
因此, 实际考虑后确认, 淘宝不是太适合新人小团队去学习试炼爬虫爬取信息.
|
|
|
|
|
|
相对而言, 京东搜索页基本同一为京东自己的页面, 京东自营, 旗舰店和商家店结构基本没有区别, 详情页结构也一致, 对爬虫比较友好(京东唯一不太友好的地方只有, 其主站页面下并没有写robots.txt). 因此考虑爬取京东.
|
|
|
|
|
|
### 搜索页爬虫
|
|
|
|
|
|
主要由`settings.yaml`和`jdSearchSeleniumSpider.py`两部分组成,`settings.yaml`是配置文件, `jdSearchSeleniumSpider.py`是代码执行主体
|
|
|
|
|
|
#### 前期工作
|
|
|
|
|
|
基本而言, 任何有搜索功能的网站都会对其搜索功能做一定的限制, 以防止单个ip过高频率的访问, 占用过多资源.
|
|
|
|
|
|
京东也不例外, 对于未登录的请求, 甚至是带着header和cookie的请求, 往往都是秉持拒绝的态度, 要求验证登录:
|
|
|
|
|
|

|
|
|
|
|
|

|
|
|
|
|
|
搜索引擎的爬虫验证一般级别都比较高, 难以绕过. 考虑到搜索页的数量相对于商品页的数量往往是少一个数量级, 且京东单个搜索词的页面量也大致在几十几百页的数量级之间, 因此使用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和价格.
|
|
|
|
|
|

|
|
|
|
|
|
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列表开头.
|