通过scrapy_redis 实现分布式爬取当当图书案例
# -*- coding: utf-8 -*-
import copy
#用scrapy_redis 不需要继承这个类了继承下面的
import scrapy
from scrapy_redis.spiders import RedisSpider
class DangdangSpider(RedisSpider):
name = 'dangdang'
allowed_domains = ['dangdang.com']
"""
在scrapy_redis中不需要设置start_url只需要从redis——key对应的队列中获取request对象
在启动程序的时候 spider并没有进行爬取 只有在请求队列中 出现请求对象的时候才开始工作
start_urls = ['http://book.dangdang.com/']
具体指令 $redis-cli -h 127.0.0.1-p 6379 主机和端口
输入ping 回复pong 链接成功
lpush dandang start_url
程序开始爬取
"""
redis_key = "dangdang"
def parse(self, response):
"""
进入当当的首页 以其左面的导航栏为请求的出发点 对书进行三层分类,
fir_sort代表一级分类,首先xpath获取其一级分类的列表(同时也获取了书对应的一级分类的名字),对其中每个对象进行遍历获取其对应的下一级》》
m_sort 代表二级分类,进入一级分类的某个元素,二级分类同样也是一个列表,对其中的标签对象进行遍历,获取每个对象的下一级》》
thi_sort 三级分类 在三级分类中除了获取三级分类的名字外,要获取三级分类对应的图书列表页的url地址 并发送请求
"""
#获取一级分类的列表并遍历
fir_sort_list = response.xpath("//div[@class='level_one 'and position()<15]")
for fir_sort in fir_sort_list:
item = {}
item["fir_sort"] = fir_sort.xpath("./dl/dt//text()").extract()
item["fir_sort"] = [i.strip() for i in item["fir_sort"] if len(item["fir_sort"])>0]
item["fir_sort"] = [i for i in item["fir_sort"] if i]
m_sort_list = fir_sort.xpath("./div//dl[@class ='inner_dl']")
for m_sort in m_sort_list:
item["m_sort"] = m_sort.xpath("./dt//text()").extract()
item["m_sort"] =list(filter(lambda x:len(x)>0,list(map(lambda x:x.strip(),item["m_sort"]))))
thir_sort_list = m_sort.xpath("./dd/a")
for thir_sort in thir_sort_list:
item["thi_sort"] = thir_sort.xpath("./@title").extract_first()
book_list_url = thir_sort.xpath("./@href").extract_first()
#获取该分类下的图书列表发送请求,为了不使item中的数据呗覆盖所以使用
#deepcopy
yield scrapy.Request(
book_list_url,
callback = self.get_book_list,
#每次获取booklist都是将上面循环的结果拷贝下来发给下个函数,防止有些字段的数据被覆盖
meta={"item":copy.deepcopy(item)}
)
def get_book_list(self,response):
"""
从列表页中获取书的 书名,书价,以及书详情页的url地址 发送请求 在详情页中获取信息
:param response:
:return:
"""
item = response.meta["item"]
book_list = response.xpath("//div[@id='search_nature_rg']/ul[@class='bigimg']/li")
for book in book_list:
item["bk_name"] = book.xpath("./a/@title").extract_first()
#detail在列表展示页面中 有些的没有所以需要请求详情页
item["bk_price"] = book.xpath("./p[@class='price']/span[@class='search_now_price']/text()").extract_first()
book_detail_url = book.xpath("//a[@class='pic']/@href").extract_first()
yield scrapy.Request(
book_detail_url,
callback = self.deal_book_detail,
meta={"item":copy.deepcopy(item)}
)
#实现图书列表页的翻页,后获取信息
next_page_url = response.xpath("//a[@title='下一页']").extract_first()
#判断是否有下一页,或者是否是最后一页再决定是否发送请求
if next_page_url:
next_page_url = "http://category.dangdang.com/" + next_page_url
yield scrapy.Request(
next_page_url,
callback = self.get_book_list,
#精神洁癖0.0传给下一页的是上个函数深拷贝传过来的,不含有这个函数新添加字段的item
meta = {"item":response.meta["item"]}
)
def deal_book_detail(self,response):
item = response.meta["item"]
item["author"] = response.xpath("//div[@class ='messbox_info']/span[1]//text()")
if item["author"]:
item["author"] = "".join(item["author"].extract())
if ":" in item["author"]:
item["author"] = item["author"].split(":")[1]
item["publisher"] = response.xpath("//div[@class ='messbox_info']/span[@dd_name='出版社']/a/text()").extract_first()
item["pub_date"] = response.xpath("//div[@class ='messbox_info']/span[contains(text(),'出版时间')]/text()").extract_first()
if item["pub_date"]:
item["pub_date"] = item["pub_date"].split(":")[1].strip()
item["bk_image"] = response.xpath("//img[@id = 'largePic']/@src").extract_first()
yield item
有关settings文件中的配置
#确保所有的爬虫通过Redis去重【生成指纹存储到dangdang:dupefilter键对应的set中】
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
#启用Redis调度存储请求队列
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
#不清除Redis队列、这样可以暂停/恢复 爬取,为false时候关闭程序redis清空
SCHEDULER_PERSIST = True
#指定用于连接redis的URL(可选)
#如果设置此项,则此项优先级高于设置的REDIS_HOST 和 REDIS_PORT
REDIS_URL = "redis://127.0.0.1:6379"
#此外ITEM_PIPELINES中可以实现将数据存储到redis中
ITEM_PIPELINES = {
'dangdang.pipelines.JdPipeline': 300,
#调用这个管道会把我们爬取的数据 存入到redis数据库中
#我们可以定义其他的管道 将数据做我们想要的操作
'scrapy_redis.pipelines.RedisPipeline': 400,
}
最后布置在不同电脑上 在通过想redis 添加redis_key 对应的值就能实现分布式爬取