python3 + scrapy爬取妹子图(meizitu.com)

原创 2018年04月16日 17:04:57

前言

在学会scrapy之前,都是用requests + BeautifulSoup + lxml来爬取的,这样也能爬到想要的东西,但缺点是代码有些乱,可能需要自己对项目进行梳理归类。而scrapy框架很好的解决了这个问题,它将爬虫的整个工序都分离开,各司其职,项目结构看起来很优雅。并且框架提供了很多非常实用的方法,不必再自己去单独写了,简直是良心。爬虫的乐趣在于爬取感兴趣的东西,下面将以爬取妹子图(meizitu.com)来实践下。

了解网站,理清爬虫思路

进入妹子图,可以看到网站首页,中间有一条美女分类的标签,如图:

这里写图片描述

然后当点进某个分类之后,会得到很多分页,每个分页有很多图片专辑,点击每个专辑进去就会看到很多图片,这个图片就是我们需要的,那大致思路可以出来了,即:

  1. 通过首页 (http://www.meizitu.com/),爬取标签名称tag_name和标签链接tag_href
  2. 通过标签链接,爬取当前标签下全部页面page_list
  3. 通过页面,爬取当前页面的图片专辑名称album_name和图片专辑链接album_href
  4. 通过专辑链接,爬取该专辑里面所有图片名称img_title、图片链接img_src及图片链接列表img_urls
  5. 通过图片链接,ImagesPipeline下载图片到设定的文件夹

通过以上思路,可以确定几点,

  1. items应该包含哪些?

    毫无疑问,tag_name tag_href page_list album_name album_href imgs img_title img_urls就是需要定义的item

  2. 爬虫的入口是什么?

    网站首页,即http://www.meizitu.com/

  3. 爬虫应该分几层?

    根据思路,我们前面 4 步,都是通过不同的链接爬取相关信息,那爬虫也相应的需要 4 层。

    第一层,爬取标签链接:parse_tag

    第二层,爬取标签下页面链接:parse_page

    第三层,爬取页面下专辑链接:parse_album

    第四层,爬取专辑下图片链接:parse_img

  4. 怎么保存图片?

    scrapy框架提供一个item pipeline来保存图片,即ImagesPipeline,我们只需要重写一个管道继承ImagesPipeline,并且重写get_media_requests(item, info) 和item_completed(results, items, info) 这两个方法即可

代码实践

1、首先定义item

items.py

import scrapy


class MeizituItem(scrapy.Item):
    # 标签名称
    tag_name = scrapy.Field()
    # 标签链接
    tag_href = scrapy.Field()
    # 进入某标签后的所有链接,加页码的
    page_list = scrapy.Field()
    # 图片专辑名称
    album_name = scrapy.Field()
    # 图片专辑链接
    album_href = scrapy.Field()
    # 照片标题
    img_title = scrapy.Field()
    # 照片链接
    img_src = scrapy.Field()
    # 照片链接集合,用于ImagesPipeline下载图片
    img_urls = scrapy.Field()

2、完成提取数据代码

mzt.py

# -*- coding: utf-8 -*-
import scrapy

from meizitu.items import MeizituItem


class MztSpider(scrapy.Spider):
    name = 'mzt'
    allowed_domains = ['meizitu.com']
    # start_urls:爬虫入口
    start_urls = ['http://meizitu.com/']

    def parse_tag(self, response):
        """
        提取标签名称和链接
        :param response:
        :return:
        """
        tags = response.xpath(".//*[@class='tags']/span/a")
        for i in tags:
            item = MeizituItem()
            tag_href = i.xpath(".//@href").extract()[0]
            tag_name = i.xpath(".//@title").extract()[0]
            item['tag_name'] = tag_name
            item['tag_href'] = tag_href
            yield scrapy.Request(url=item['tag_href'], meta={'item': item}, callback=self.parse_page)

    def parse_page(self, response):
        """
        提取标签下链接
        :param response:
        :return:
        """
        item = response.meta['item']
        # 进入某个标签后,爬取底部分页按钮
        page_lists = response.xpath(".//*[@id='wp_page_numbers']/ul/li")
        # 获取底部分页按钮上的文字,根据文字来判断当前标签页下总共有多少分页
        page_list = page_lists.xpath('.//text()')
        # 如果当前标签页下有多个页面,则再根据第一个按钮是否为“首页”来进行再次提取,因为这里有的页面第一个按钮是首页,有的第一个按钮是“1”
        if len(page_lists) > 0:
            if page_list[0].extract() == '首页':
                page_num = len(page_lists) - 3
            else:
                page_num = len(page_lists) - 2
        else:
            page_num = 1

        # 根据当前标签页的链接,来拼成带页码的链接
        if '_' in item['tag_href']:
            index = item['tag_href'][::-1].index('_')
            href_pre = item['tag_href'][:-index]
        else:
            if page_num == 1:
                href_pre = item['tag_href'].split('.html')[0]
            else:
                href_pre = item['tag_href'].split('.html')[0] + '_'
        for i in range(1, page_num + 1):
            item = response.meta['item']
            if page_num == 1:
                href = href_pre + '.html'
            else:
                href = href_pre + str(i) + '.html'
            item['page_list'] = href
            yield scrapy.Request(url=item['page_list'], meta={'item': item}, callback=self.parse_album)

    def parse_album(self, response):
        """
        提取专辑名称和专辑链接
        :param response:
        :return:
        """
        albums = response.xpath(".//*[@class='pic']")
        for album in albums:
            item = response.meta['item']
            album_href = album.xpath(".//a/@href").extract()[0]
            album_name = album.xpath(".//a/img/@alt").extract()[0]
            item['album_name'] = album_name
            item['album_href'] = album_href
            yield scrapy.Request(url=item['album_href'], meta={'item': item}, callback=self.parse_img)

    def parse_img(self, response):
        """
        提取图片名称和链接
        :param response:
        :return:
        """
        img_list = response.xpath(".//*/p/img")
        for img in img_list:
            item = response.meta['item']
            img_title = img.xpath(".//@alt").extract()[0]
            if img_title == '':
                for i in range (1, len(img_list+1)):
                    img_title = item['album_name'] + '_' + str(i)
            else:
                img_title = img_title
            img_urls = img.xpath(".//@src").extract()
            img_src = img.xpath(".//@src").extract()[0]
            item['img_title'] = img_title
            item['img_src'] = img_src
            item['img_urls'] = img_urls
            yield item

这里面都是数据提取的过程,比较麻烦的在parse_page,各个标签下的链接结构不一样,导致在拼链接的时候需要判断很多种情况,这些的话可以一步一步的调试,遇到错误就多加判断。

下面说下我在学习scrapy时不太理解的地方:

  1. yield是干嘛的,大概就是通过yield 可以给 item 返回数据 也可以发送下一个的 request 请求
  2. parse_tagyield scrapy.Request(url=item['tag_href'], meta={'item': item}, callback=self.parse_page),可以理解为把item['tag_href']作为url,传递给parse_page这个request请求,得到新的response用以提取数据,meta={'item': item}可以把之前收集到的item数据传递到下一个方法继续使用和收集
  3. item = response.meta['item']就是接收传递过来的item数据,可以继续使用和收集

3、保存图片

以下为抄其他博主的:

保存图片需要在自定义的 ImagePipeline 类中重载方法:get_media_requests(item, info)item_completed(results, items, info)Pipeline将从 item 中获取图片的 URLs 并下载它们,所以必须重载get_media_requests,并返回一个Request对象,这些请求对象将被 Pipeline 处理,当完成下载后,结果将发送到item_completed方法,这些结果为一个二元组的 list,每个元祖的包含(success, image_info_or_failure)。 * success: boolean值,true表示成功下载 * image_info_or_error:如果success=trueimage_info_or_error词典包含以下键值对。失败则包含一些出错信息。 * url:原始 URL * path:本地存储路径 * checksum:校验码

pipelines.py

import scrapy
from scrapy import log
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem


class MztImagesPipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        for image_url in item['img_urls']:
            yield scrapy.Request(image_url)

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem("该Item没有图片")
        return item

仅仅这些还是不够的,你还需要设置下图片保存的路径、图片大小限制、过期天数等等,在settings.py中添加以下代码

IMAGES_STORE = r'E:\\mzt'    # 图片存储路径
IMAGES_EXPIRES = 90             # 过期天数
IMAGES_MIN_HEIGHT = 100         # 图片的最小高度
IMAGES_MIN_WIDTH = 100          # 图片的最小宽度

并且在settings.py中的ITEM_PIPELINES加上'meizitu.pipelines.MztImagesPipeline': 301

这样当图片的宽带或高度小于最小的时候就不会下载了,下载成功的图片会保存在E:\\\mzt\\full中。

4、爬虫运行

默认的运行需要在命令行中执行 scrapy crawl spider_name,这样的缺点是不能在IDE里面debug代码,比较不方便。所以,可以自己在项目里面新建一个run.py,以后执行这个文件就相当于启动爬虫。

run.py

from scrapy import cmdline

# cmdline.execute("scrapy crawl mzt".split())    # 直接执行,log显示在控制台
cmdline.execute("scrapy crawl mzt -s LOG_FILE=mzt.log".split())  # log保存在项目里面的mzt.log文件

以上两条语句都可以启动爬虫,可根据是否需要保存 log 来选择,没有选择的注释掉。

爬虫结果展示

迷之马赛克

这里写图片描述

后续优化

  1. 本来我的本意是图片按标签分类,放在不同的文件夹,然后名称以网页中的名称命名,后续可以按此归类
  2. 当我写这篇文章的时候,才发现meizitu网站首页底部是有分页的,尼玛我爬的时候没把浏览器最大化没看到,导致走了无数个弯路,所以如果纯粹的为了爬取结果的话还是多观察观察网站,如果是练手的话怎么麻烦怎么搞吧,当作练习了。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010978840/article/details/79963193

分布式爬虫以及Scrapy源码剖析

信息时代,数据为王,互联网包含了迄今为止最有用的数据集,并且大部分可以免费公开访问,但是由于数据被嵌入在众多网站的结构和样式中导致难以被复用,应运而生出现了网络爬虫,使用程序自动获取互联网上的资源。本系列课程将带你开发自动化程序实现数据自动采集,针对众多网站防止数据被获取采取防爬虫方案,课程中包含对防爬策略所有解决方法,以及如何提高爬虫性能,爬虫方向真正做到“遇鬼杀鬼,遇神杀神,所向披靡”。
  • 2017年09月22日 10:25

Python3 实现妹子图爬虫

一.项目说明 1.项目介绍 本项目通过使用 Python 实现一个妹子图图片收集爬虫,学习并实践 BeautifulSoup、Request,Urllib 及正则表达式等知识。在项目开发过程中采用...
  • seven_2016
  • seven_2016
  • 2016-08-18 22:57:13
  • 16697

Python使用Scrapy爬取妹子图

Python Scrapy爬虫,听说妹子图挺火,我整站爬取了,上周一共搞了大概8000多张图片。和大家分享一下。 核心爬虫代码 1 2 3 4 5 6...
  • qq_30175203
  • qq_30175203
  • 2016-06-13 12:51:49
  • 2212

Python Scrapy爬虫,整站爬取妹子图

Python Scrapy爬虫,听说妹子图挺火,我整站爬取了,上周一共搞了大概8000多张图片。和大家分享一下。 项目地址:https://github.com/ZhangBohan/fun_cra...
  • ztsghjls
  • ztsghjls
  • 2016-06-30 11:31:42
  • 1519

python scrapy 爬取妹子图的照片

主要描述在windows 上如何用scrapy抓取煎蛋网妹子图所有的图片下载。 软件准备:winpython,啥都不说了,ipython很好用。 安装scrapy,进入winpytho...
  • lvronglee
  • lvronglee
  • 2015-03-15 09:18:50
  • 2433

Python3爬虫之破解图片防盗链

import requests, os, os.path, urllib, random from bs4 import BeautifulSoup def get_soup(url): ""...
  • qq_33733970
  • qq_33733970
  • 2017-09-07 07:54:53
  • 2320

python爬虫系列三:爬取糗百成人的妹子图片(scrapy框架+正则)

python爬虫系列三:爬取糗百成人的妹子图片(scrapy框架+正则)windows下scrapy的安装具体的安装使用,详见scrapy官网:http://scrapy-chs.readthedoc...
  • u010445540
  • u010445540
  • 2017-03-03 13:13:24
  • 1217

使用scrapy爬取妹子图(一)

初学python爬虫框架scrapy,给出了一个scrapy入门的练手项目的整体实现过程,实现了从妹子图网站(meizitu.com)爬取妹子图片并分类存储的功能。...
  • aaronjny
  • aaronjny
  • 2017-06-22 14:10:20
  • 1455

python3爬取煎蛋网妹子图

python,语法简洁结构清晰,十分让人钟爱。 我今天下午写了一个爬虫,嗯,爬取煎蛋网妹子图的程序,相信许多人都有这个想法,嘿嘿嘿。。。在这强调一下啊,我在这是单纯的分享技术,那为什么要爬取煎蛋网的妹...
  • Lingdongtianxia
  • Lingdongtianxia
  • 2017-09-02 20:57:24
  • 2312

爬妹子图.python

  • 2018年02月11日 11:47
  • 532B
  • 下载
收藏助手
不良信息举报
您举报文章:python3 + scrapy爬取妹子图(meizitu.com)
举报原因:
原因补充:

(最多只允许输入30个字)