scrapy抓取全站产品,存储数据到mongo,下载产品源码以及图片,生成excel,以及采用scrapy+redis分布式实现过程

简单粗暴,不说别的废话,今天我们使用scrapy来爬 https://www.tous.com/us-en/jewelry 这个站的产品信息。

先来了解一下 scrapy这个爬虫框架 看下面的图片

 

 

1、引擎(Scrapy): 用来处理整个系统的数据流处理, 触发事务(框架核心)。
2、调度器(Scheduler): 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。
3、下载器(Downloader): 用于下载网页内容, 并将网页内容返回给蜘蛛。
4、爬虫(Spiders): 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。
5、项目管道(Pipeline): 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
6、下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
7、爬虫中间件(Spider Middlewares): 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
8、调度中间件(Scheduler Middewares): 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

 

上面可能说的比较专业术语一些,没那么通俗易懂,下面是我总结的,其实很简单只要记住:

1、item定义一个类,里面定义我们需要提取的网页信息

2、爬虫文件调用这个类item,实例化,给预先定义好的变量进行赋值,提去网页信息,最后将结果返回给pipeline文件。

3、pipeline文件是用来数据库处理,比如存数据库,下载文件,图片等。

4、爬虫中间件middleware ,有时候我们容易被封号,这时候代理ip的处理就是在这个文件。

5、最后将pipeline定义的类以及middleware定义的类,需要使用的东西,写好之后,去setting对应的地方注册,就可以启用整个爬虫。

6、还要将setting文件中的ROBOTSTXT_OBEY改为False,以及想应的请求头信息

 

以上应该可以很好明白把,我们就按照这个步骤一步一步来敲代码,敲完以上代码,我们运行scrapy crawl tou 即可运行啦!

 

安装scrapy也是很容易的啦 pip install scrapy 即可,我的开发环境是centos.

 

先创建工程 scrapy stratproject tous

然后创建spider  scrapy genspider tou tous.com 

首先第一步我们先定义item

class TousItem(scrapy.Item):
    file_urls = scrapy.Field()
    files = scrapy.Field()
    image_urls = scrapy.Field()
    images = scrapy.Field()
    current_time = scrapy.Field()
    product_url = scrapy.Field()
    name = scrapy.Field()
    marketprice = scrapy.Field()
    price = scrapy.Field()
    sku = scrapy.Field()
    des = scrapy.Field()
    image = scrapy.Field()
    v_categories_name_1 = scrapy.Field()
    v_categories_name_2 = scrapy.Field()
    v_categories_name_3 = scrapy.Field()
    v_categories_name_4 = scrapy.Field()
    v_categories_name_5 = scrapy.Field()
    v_categories_name_6 = scrapy.Field()
    v_categories_name_7 = scrapy.Field()
    shop = scrapy.Field()

 

第二步:我们使用item这个类,来编写我们的tou.py提去网页中我们需要的数据,最后返回给pipelines

class TouSpider(CrawlSpider):
    name = 'tou'
    allowed_domains = ['tous.com']
    start_urls = ['https://www.tous.com/us-en/jewelry']

    rules = (
        Rule(LinkExtractor(allow=r'https://www.tous.com/us-en/[a-zA-Z-0-9-]+/'),callback='parse_item',follow=True),
        Rule(LinkExtractor(allow=r'https://www.tous.com/us-en/[a-zA-Z-0-9-]+/'), follow=True),
        Rule(LinkExtractor(allow=r'https://www.tous.com/us-en/[a-zA-Z-0-9-]+/[a-zA-Z-0-9-]+/'), follow = True),
        Rule(LinkExtractor(allow=r'https://www.tous.com/us-en/[a-zA-Z-0-9-]+/[a-zA-Z-0-9-]+/\?p=\d+'), follow = True),
        Rule(LinkExtractor(allow=r'https://www.tous.com/us-en/[a-zA-Z-0-9-]+/\?p=\d+'), follow = True),
    )

    def parse_item(self, response):
        try:
            item = TousItem()
            item["name"] = response.css("h1#products-name::text").extract()[0].strip()
            item["sku"] ='N'+response.css("span#product-sku::text").extract()[0]
            item["product_url"] =re.findall(r'<meta\s*property=\"og:url\"\s*content=\"(.*?)\"',str(response.body))[0]
            item["price"]= response.css("div.product-shop-inner div.price-box span.regular-price span.price::text").extract()[0]
            item["marketprice"]= response.css("div.product-shop-inner div.price-box span.regular-price span.price::text").extract()[0]
            item["des"]= response.css("div.easytabs-content ul li::text").extract()[0]
            try:
                all_image = response.css("a.thumbnail").extract()
                all_img = []
                for i in range(0,len(all_image)):
                    all_img.append(re.findall(r'<img\s*src=\"(.*?)\"',all_image[i])[0].replace("/140x/","/1000x/"))
                item["image"]= all_img
            except:
                all_img = []
                all_img.append(re.findall(r'<meta\s*property=\"og:image\"\s*content=\"(.*?)\"',str(response.body))[0])
            item["current_time"]= time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())
            try:
                classify = response.css("div.breadcrumbs ul li a::text").extract()
                item["v_categories_name_1"] = classify[0] if len(classify) > 0 else ""
                item["v_categories_name_2"] = classify[0] if len(classify) > 1 else ""
                item["v_categories_name_3"] = classify[0] if len(classify) > 2 else ""
                item["v_categories_name_4"] = classify[0] if len(classify) > 3 else ""
                item["v_categories_name_5"] = classify[0] if len(classify) > 4 else ""
                item["v_categories_name_6"] = classify[0] if len(classify) > 5 else ""
                item["v_categories_name_7"] = classify[0] if len(classify) > 6 else ""
            except:
                print("目录")
            item["shop"]= "https://www.tous.com/us-en/jewelry"
            item["file_urls"] = [item["product_url"]]
            item["image_urls"] = all_img
            print(item["file_urls"])
            print(item["image_urls"])
            print("是产品页面")
            print("--------------------------")
            yield item
        except:
            print("不是产品页面")

 

第三步:我们编写pipe管道文件 处理爬虫返回的信息 这边我们实现两个功能:1、将数据存进mongo数据库 2、自定义命名文件的存储形式

class MongoDBPipeline(object):
    def __init__(self):
        client = pymongo.MongoClient(settings['MONGODB_SERVER'], settings['MONGODB_PORT'])
        db = client[settings['MONGODB_DB']]
        self.product_col = db[settings['MONGODB_COLLECTION']]


    def process_item(self, item, spider):
        result = self.product_col.find_one({"sku": item["sku"], "product_shop": item["shop"]})
        print("查询结果:"+str(result))
        try:
            if len(result) > 0:
                mongoExistFlag = 1
        except Exception as e:
            mongoExistFlag = 0
        if (mongoExistFlag == 0):
            print("这条记录数据库没有 可以插入")
            self.product_col.insert_one(
                {"sku": item["sku"], "img": item["image"], "name": item["name"], "price": item["price"], "marketprice": item["marketprice"], "des": item["des"],
                 "addtime": item["current_time"], "product_url": item["product_url"], "v_specials_last_modified": "",
                 "v_products_weight": "0", "v_last_modified": "", "v_date_added": "", "v_products_quantity": "1000",
                 "v_manufacturers_name": "1000", "v_categories_name_1": item["v_categories_name_1"],
                 "v_categories_name_2": item["v_categories_name_2"], "v_categories_name_3": item["v_categories_name_3"],
                 "v_categories_name_4": item["v_categories_name_4"], "v_categories_name_5": item["v_categories_name_5"],
                 "v_categories_name_6": item["v_categories_name_6"], "v_categories_name_7": item["v_categories_name_7"],
                 "v_tax_class_title": "Taxable Goods", "v_status": "1", "v_metatags_products_name_status": "0",
                 "v_metatags_title_status": "0", "v_metatags_model_status": "0", "v_metatags_price_status": "0",
                 "v_metatags_title_tagline_status": "0", "v_metatags_title_1": "", "v_metatags_keywords_1": "",
                 "v_metatags_description_1": "", "product_shop": item["shop"]})
            print("数据插入mongo数据库成功")
            print("---------------------------------------")
            # time.sleep(2)
        if (mongoExistFlag == 1):
            print("该字段已在数据库中有记录")
            print("该页面是产品,准备存进磁盘")
            print("------------------------------------------")
            # time.sleep(2)
        return item

class DownloadImagePipeline(ImagesPipeline):
    def get_media_requests(self, item, info):  # 下载图片
        print("进入图片的 get_media_requests")
        print(item["image_urls"])
        for image_url in item['image_urls']:
            yield Request(image_url, meta={'item': item, 'index': item['image_urls'].index(image_url)})

    def file_path(self, request, response=None, info=None):
        print("下载图片")
        item = request.meta['item']  # 通过上面的meta传递过来item
        index = request.meta['index']
        # print(item)
        # print(index)
        image_name = item["sku"]+"_"+str(index)+".jpg"
        print("-----------------------------------")
        print("image_name:"+str(image_name))
        print("-----------------------------------")
        time.sleep(1)
        down_image_name = u'full/{0}/{1}'.format(item["sku"], image_name)
        print("----------------------------------")
        print("down_image_name:"+str(down_image_name))
        print("----------------------------------")
        time.sleep(1)
        return down_image_name

class DownloadFilePipeline(FilesPipeline):
    def get_media_requests(self, item, info):
        for file_url in item['file_urls']:
            yield Request(file_url, meta={'item': item, 'index': item['file_urls'].index(file_url)})

    def file_path(self, request, response=None, info=None):
        print("下载文件")
        item = request.meta['item']  # 通过上面的meta传递过来item
        # index = request.meta['index']
        file_name = item["sku"]+".html"
        print("-----------------------------------")
        print("file_name:"+str(file_name))
        print("-----------------------------------")
        time.sleep(1)
        down_file_name = u'full/{0}/{1}'.format(item["sku"], file_name)
        print("----------------------------------")
        print("down_file_name:"+str(down_file_name))
        print("----------------------------------")
        time.sleep(1)
        return down_file_name

 

第四步:有时候我们如果用自己的ip地址,不断访问,可能最后会被禁止访问,那咋办呢,使用代理呀,这个文件我们在middleware文件编写一个类

class TouspiderProxyIPLoadMiddleware(object):
    def __init__(self):
        self.proxy = ""
        self.expire_datetime = datetime.datetime.now() - datetime.timedelta(minutes=1)
        # self._get_proxyip()

    def _get_proxyip(self):
        f = open("proxy.txt")
        proxys = f.read().split("\n")
        p = random.sample(proxys, 1)[0]
        print("proxy:", p)
        self.proxy = p
        self.expire_datetime = datetime.datetime.now() + datetime.timedelta(minutes=1)

    def _check_expire(self):
        if datetime.datetime.now() >= self.expire_datetime:
            self._get_proxyip()
            print("切换ip")

    def process_request(self, spider, request):
        self._check_expire()
        request.meta['proxy'] = "http://" + self.proxy

最后一步当然是去setting里面注册啦,注册代理,注册mongo,注册文件以及图片的自定义:

DOWNLOADER_MIDDLEWARES = {
   # 'tous.middlewares.TousDownloaderMiddleware': 543,
    'tous.middlewares.TouspiderProxyIPLoadMiddleware':543,
}

ITEM_PIPELINES = {
   # 'tous.pipelines.TousPipeline': 300,
    'tous.pipelines.MongoDBPipeline':1,
    'tous.pipelines.DownloadFilePipeline':2,
    'tous.pipelines.DownloadImagePipeline':3,
}
FILES_STORE = 'files'
IMAGES_STORE = "images"
MONGODB_SERVER = '数据库地址'
MONGODB_PORT = 27017
# 设置数据库名称
MONGODB_DB = 'product'
# 存放本数据的表名称
MONGODB_COLLECTION = 'product'

 

然后最后再更改一下这两个东西即可:请求头 和 ROBOTSTXT_OBEY 改为false

ROBOTSTXT_OBEY = False


DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
  'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
  'Reference':'https://www.tous.com/us-en/jewelry',
}

  

然后我们 scrapy crawl tou 就可以爬了 以下是下载好的图片以及文件 存放格式

 

 

 

 

 

 这时候等你运行完之后,图片和文件都下好啦,这时候excel要怎么弄呢?

有两种方法:第一种我们直接在运行的时候带参数,第二种使用python带的库。

 

第一种:运行的时候带参数,这边生成的都是item类里面定义好的字段。

scrapy crawl tou -o tou.csv

以下是部分excel截图

 

 

第二种,比较推荐使用,我们可以自定义一些字段,我们使用这个库openpyxl,将mongo数据库里面的数据读出导成excel

from openpyxl import Workbook

这边就不做介绍啦 相对简单 以下是运行完excel的部分截图

 

以上就是一个爬虫的完整过程啦!

 

scrapy+redis

大家都知道scrapy是python主流的爬虫框架,单单使用scrapy框架,还不能快速爬虫做到分布式,所以呢,要怎么做呢?这边我简单介绍一下!

虽然scrapy能做的事情很多,但是要做到大规模的分布式应用则捉襟见肘。我么可以scrapy的队列调度,将起始的网址从start_urls里分离出来,改为从redis读取,多个客户端可以同时读取同一个redis,从而实现了分布式的爬虫。

 

我们先说一下大概分布式的一个思路:

比如我们有十台机子:那其中的一台作为Master端(核心服务器),其余九台作为Slaver端(爬虫程序执行端) 。

Master端(核心服务器) :搭建一个Redis数据库,不负责爬取,只负责url指纹判重、Request的分配,以及数据的存储

Slaver端(爬虫程序执行端) :负责执行爬虫程序,运行过程中提交新的Request给Master

(其实以上过程就是想socket通讯,server端有完整的全站的地址,server负责给client端分发数据,client进行点击并将结果返回给server,sever进行相应的查重)

 

实现过程:

首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;

Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据。

 

scrapy-redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作scrapy-redis都已经帮我们做好了,我们只需要继承RedisCrawlSpider、指定redis_key就行了。

怎么样,应该很好理解吧,这边下面我们开始!

 

第一步:肯定是要安装scrapy-redis

pip install scrapy-redis

 

第二步:导入scrapy-redis,集成RedisCrawlSpider

from scrapy_redis.spiders import RedisCrawlSpider

 

第三步:指定redis_key

redis_key = 'tou:start_urls'

RedisCrawlSpider类 不需要写start_urls

必须指定redis_key,即启动爬虫的命令,参考格式:redis_key = '类名:start_urls'

根据指定的格式,start_urls将在 Master端的 redis-cli 里 lpush 到 Redis数据库里,RedisSpider 将在数据库里获取start_urls。

 

第四步:然后要去setting文件夹配置一下我们的redis

# 过滤器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 调度状态持久化
SCHEDULER_PERSIST = True
# 请求调度使用优先队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
REDIS_ENCODING = "utf-8"
REDIS_HOST='ip地址'
REDIS_PORT=6379
REDIS_PARAMS = {'password':'redis数据库密码'}

 

第五步:在ITEM_PIPELINES注册一下我们的redis数据库

'scrapy_redis.pipelines.RedisPipeline': 4,

 

然后我们运行scrapy crawl tou 发现爬虫们处于等待时刻

然后在Master端的redis-cli输入push指令,

 

然后分布式爬虫就这么愉快的开始啦!这边是一个运行过程的截图,我的master是win10端,Slaver端是linux

slave服务器处于等待状态,当右边的主服务push一个url入口,左边的两个便有一个获取到这个链接的request,然后返回更多的request到请求队列中,然后slave服务器便开始从队列获取请求开始爬了。

 

redis保存数据如图所示: 

 

 

 

 

 

这个就是一个分布式的爬虫过程的一个展示!速度确实上去不少!

 

posted @ 2019-01-17 17:26  WangHello  阅读(353)  评论(0编辑  收藏  举报