爬虫进阶:CrawlSpider爬取169ee全站美女图片

CrawlSpider

前面,我们用了scrapy中的CrawlSpider爬取了糗事百科中大量段子数据。

但是,qiubai这个爬虫没有充分利用CrawlSpider的优点。其实,在qiubai这个爬虫里面我们只是把CrawlSpider当做普通的Spider用而已。

CrawlSpider继承自Spider,提供了Rule和LinkExtractor,使得爬虫框架能够自动按照规则提取Response中所有符合条件的链接,并且跟进这些链接继续爬取。从而,CrawlSpider非常适合爬取比较规则的网站。并且,我们只需写上合适的Rule和LinkExtractor,就能够避免自己在parse方法中写一大堆提取逻辑来提取链接和数据。

使用了CrawlSpider的特性(Rule和LinkExtractor)之后,我们只需要关注那些我们感兴趣的页面,编写parse方法从这些页面对应的Response上提取item数据即可。继续跟进和爬取页面的事情让CrawlSpider做就好。

让我们再来看一下Rule和LinkExtractor的作用:

首先,我们看一下CrawlSpider的主要属性和方法:

rules:列表,定义了从Response提取符合要求的链接的Rule对象。
parse_start_url:CrawlSpider默认的回调函数,我们在使用CrawlSpider时,不应该覆盖parse方法,而应该覆盖这个方法。因为CrawlSpider使用了parse函数来处理自己的逻辑,所以我们不能覆盖parse方法。

其中,Rule有以下几个参数:

link_extractor:LinkExtractor对象,用于定义需要提取的链接。Link Extractors是链接提取器,用来从返回网页的Response中提取符合要求的链接。
callback:回调函数,当link_extractor提取到符合条件的链接时,就会把该链接继续处理获得相应的Response,然后把该Response传递给这个函数处理。由于CrawlSpider内部使用了parse方法,所以我们自己设置的该回调函数一般不能为parse方法。callback不为空时,follow为false,表示该提取的链接对应的Response应该传递给callback设置的函数处理,而不应该继续用于跟进。
follow:布尔值,指定了根据link_extractor规则从response提取的链接是否需要跟进,跟进的意思就是指让CrawlSpider对这个提取的链接进行处理,继续拿到该链接的Response,然后继续使用所有的rule去匹配和提取这个Response,然后再不断的做这些步骤。callback为空时,follow默认为true,否则就是false。
process_links:函数或者函数名,用于过滤link_extractor提取到的链接。
process_request:函数或者函数名,用于过滤Rule中提取到的request。

其中,LinkExtractor对象主要有以下几个参数:

allow:字符串或元组,满足括号中所有的正则表达式的那些值会被提取,如果为空,则全部匹配。
deny:字符串或元组,满足括号中所有的正则表达式的那些值一定不会被提取。优先于allow参数。
allow_domains:字符串或元组,会被提取的链接的域名列表。
deny_domains:字符串或元组,一定不会被提取链接的域名列表。
restrict_xpaths:字符串或元组,xpath表达式列表,使用xpath语法和allow参数一起提取链接。
restrict_css:字符串或元素,css表达式列表,使用css语法和allow参数一起提取链接。

分析网页结构

进169ee网我们发现,网站总的来说有三种类型的链接,如图:

这里写图片描述

编写Rule和LinkExtractor处理上面类型的链接

所以我们在rules变量可以定义不同的rule分别对这三种链接进行提取和跟进。

  1. 对于相册列表页链接,因为页面中全是相册列表,所以我们只需要跟进该链接即可;
  2. 对于相册链接,由于里面都是图片,也就是说,页面里面包含我们感兴趣的数据,所以这样的页面,我们应该设置callback函数对Response进行处理,提取出我们需要的数据。而不仅仅是简单的跟进该链接。
  3. 对于相册翻页链接,由于翻页之后,页面其实跟相册链接页面一样,所以,我们需要把该类型链接的处理设置成跟2中一样。

最终,我们设置的rule列表如下:

Rule(SgmlLinkExtractor(allow=(r'^http://www.169ee.com/[a-z]+$',))),
Rule(SgmlLinkExtractor(allow=(r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/[0-9]*?\.html$', r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/\d*?_\d*?\.html$')), callback='parse_album'))

更详细的可以参见github源码!

编写parse_album函数,提取相册中的图片

设置好了rules之后,CrawlSpider会自动跟进链接了,所以我们集中精力编写提取图片的代码。

我们进入相册页面查看网页结构,发现,xpath表达式为//div[@id="content"]/div[@class="big-pic"]/div[@class="big_img"]的div标签中的每一个p标签里面都包含了一张图片。如,

这里写图片描述

定义Item

所以,提取这里面的所有图片链接,然后提取网页的title作为相册名字,从相册链接中提取年月日,和相册id,再从图片的链接中提取出图片的id。然后,我们定义的item如下:

class OnesixnineItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    _id = scrapy.Field()  # 该图片在该相册中的id
    title = scrapy.Field()  # 该相册的名字
    year = scrapy.Field()  # 该图片上传的年份
    folder_num = scrapy.Field()  # 该图片是该年份的第几个相册
    month_day = scrapy.Field()  # 该相册是该年份的几月几日创建的
    url = scrapy.Field()  # 该图片的url
    album_id = scrapy.Field()
    album_url = scrapy.Field()

提取图片

定义好item之后,我们继续提取图片信息。编写提取函数如下:

class OnesixnineSpider(CrawlSpider):
    name = "onesixnine"

    allowed_domains = ['www.169ee.com', '724.169pp.net']

    start_urls = ['http://www.169ee.com']

    year_monthday_albumid_pattern = re.compile(r'http://www\.169ee\.com/[a-z]+/(\d*?)/(\d*?)/(\d*?)\.html')

    image_year_foldernum_imageid_pattern = re.compile(r'http://724\.169pp\.net/169mm/(\d*?)/(\d*?)/(\d*?)\.[a-z]*?')

    rules = (Rule(LinkExtractor(allow=(r'^http://www.169ee.com/[a-z]+$',))),
             Rule(LinkExtractor(
                 allow=(r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/[0-9]*?\.html$',
                        r'^http://www.169ee.com/[a-z]*?/20[0-9]*?/[0-9]*?/\d*?_\d*?\.html$')),
                 callback='parse_album'))

    def parse_album(self, response):
        img_srcs = response.xpath(
            '//div[@id="content"]/div[@class="big-pic"]/div[@class="big_img"]/p/img/@src').extract()
        if not img_srcs:
            return
        page_r_index = response.url.rfind('_')
        slash_r_index = response.url.rfind('/')
        if page_r_index > slash_r_index:
            # 当前页面链接中存在分页的部分,去掉当前链接中的分页部分
            album_url_prefix, album_url_suffix = os.path.split(response.url)
            album_url = response.urljoin(re.compile(r'_\d+').sub('', album_url_suffix))
        else:
            album_url = response.url
        # 从当前页面链接中解析出该相册的年、月日、相册id
        year_monthday_albumid_match = self.year_monthday_albumid_pattern.search(album_url)
        if not year_monthday_albumid_match:
            return
        year = year_monthday_albumid_match.group(1)
        month_day = year_monthday_albumid_match.group(2)
        album_id = year_monthday_albumid_match.group(3)
        for img_src in img_srcs:
            item = OnesixnineItem()
            item['title'] = response.xpath('/html/head/title/text()').extract_first()
            item['year'] = year
            image_year_foldernum_imageid_match = self.image_year_foldernum_imageid_pattern.search(img_src)
            if not image_year_foldernum_imageid_match:
                continue
            item['folder_num'] = image_year_foldernum_imageid_match.group(2)
            image_local_id = image_year_foldernum_imageid_match.group(3)
            item['_id'] = year + month_day + album_id + item['folder_num'] + image_local_id
            item['month_day'] = month_day
            item['album_id'] = album_id
            item['album_url'] = album_url
            item['url'] = img_src
            yield item

编写pipeline,保存图片

提取了item之后,scrapy会把Spider中产生的item转移给pipeline处理。所以我们可以在pipeline中保存图片。同时,我们可以保存图片信息到MongoDB中,待日后查看。

编写的pipeline如下:

class OnesixninePipeline(object):
    logger = logging.getLogger('OnesixninePipeline')
    connection = pymongo.MongoClient('localhost', 27017)

    def __init__(self):
        self.logger.info('pipeline init')
        self.db = self.connection.scrapy  # 切换到scrapy数据库
        self.collection = self.db.onesixnine  # 获取到onesixnine集合

    def process_item(self, item, spider):
        self.save_image(item)
        return item

    def save_image(self, item):
        if not item:
            return
        if os.path.exists(os.path.dirname(settings.DEFAULT_OUTPUT_FOLDER)):
            save_folder = settings.DEFAULT_OUTPUT_FOLDER
        else:
            save_folder = settings.CANDIDATE_DEFAULT_OUTPUT_FOLDER
        if not os.path.exists(save_folder):
            os.mkdir(save_folder)

        # 获取图片扩展名
        ext = os.path.splitext(item['url'])[1]
        # 得到图片保存的名字
        image_save_path = os.path.join(save_folder,
                                       item['year'] + '_' + item['month_day'] + '_' + item['folder_num'] + '_' + item[
                                           'album_id'] + '_' + item['_id'] + ext)
        urllib.request.urlretrieve(item['url'], image_save_path)
        if not self.connection:
            return item
        self.collection.save(item)

    def __del__(self):
        self.logger.info('pipeline exit!')

翻页抓取

我们还漏了一步。在相册链接页面,我们抓取了该页面所有的图片信息,但是一个相册有很多页,我们需要翻页抓取才不会漏掉相册中的图片。所以,我们通过xpath表达式,提取出下一页的链接://div[@id="content"]/div[@class="big-pic"]/div[@class="dede_pages"]/ul/li[last()]/a/@href,最终,翻页的处理为:

next_page_in_album = response.xpath('//div[@id="content"]/div[@class="bigpic"]/div[@class="dede_pages"]/ul/li[last()]/a/@href').extract_first()
if next_page_in_album and next_page_in_album != '#':
    next_page_url = response.urljoin(next_page_in_album)
    yield Request(next_page_url, callback=self.parse_album)

运行,查看结果

使用scrapy crawl onesixnine运行,可以自行查看运行结果

源码

源码在github上

喜欢的可以star,欢迎提issue或者pull request。

喜欢的可以关注微信公众号:
这里写图片描述

参考

  1. 我自己的头条号:爬虫进阶:CrawlSpider爬取169ee全站美女图片
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的 Python 爬虫教程,可以批量爬取指定网站的图片: 首先,我们需要安装 requests 和 BeautifulSoup 这两个库。可以通过以下命令进行安装: ``` pip install requests pip install beautifulsoup4 ``` 接下来,我们需要编写代码。以下是一个示例代码,可以爬取指定网站的图片并保存到本地: ```python import requests from bs4 import BeautifulSoup import os # 爬取图片的网站 url = "https://www.example.com/" # 请求头,模拟浏览器访问 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"} # 发送请求,获取网页内容 response = requests.get(url, headers=headers) soup = BeautifulSoup(response.text, "html.parser") # 创建保存图片的文件夹 if not os.path.exists("images"): os.makedirs("images") # 遍历所有的图片标签,获取图片链接并保存到本地 for img in soup.find_all("img"): img_url = img.get("src") if img_url.startswith("http"): response = requests.get(img_url) with open("images/" + os.path.basename(img_url), "wb") as file: file.write(response.content) print("已保存图片:", img_url) ``` 在上述代码中,我们首先定义了要爬取的网站的 URL。然后,我们使用 requests 库发送一个 GET 请求,获取网页的 HTML 内容。接着,我们使用 BeautifulSoup 库对 HTML 进行解析,获取所有的图片标签。最后,我们使用 requests 库再次发送 GET 请求,获取图片的二进制数据,并保存到本地的 images 文件夹中。 注意,这里我们使用了一个 if 判断来过滤掉非 HTTP 开头的图片链接,以避免出现下载错误的情况。 为了更好的用户体验,代码中还加入了一些注释,方便大家理解。 希望这个简单的 Python 爬虫教程能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值