利用scrapy编写琉璃神社爬虫

“要在战争中学习战争!”

在scrapy的官方文档中是使用了创建一个项目(scrapy startproject XXX)这种方式来举例子作为介绍的,然而实际上不需要创建一个project,而可以通过脚本直接启动,这一方面可以更灵活地处理网页内容,也可以避免产生一些用不到的文件(例如要是在这个爬虫之中用不到middle ware那么project中的middleware就是没有用的)。

 

项目构成(所有文件在同级目录内):

#脚本需要的依赖
requirements.txt

#自动安装依赖的脚本
configure.py

#pipeline和item
pipelines.py
llss_items.py

#用于启动爬虫的脚本,spider也在其中定义
llss_start.py

首先看一下items的定义

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

class LlssAllItem(scrapy.Item):
    title = scrapy.Field()
    post_date = scrapy.Field()
    abstract = scrapy.Field()
    magnet_with_prefix = scrapy.Field()
    magnet_without_prefix = scrapy.Field()
    image_urls = scrapy.Field()
    image_type = scrapy.Field()

item[‘title’]用于储存文章的标题

item[‘post_date’]用于储存发布时间

item[‘abstract’]用于储存文章主要内容(即对番剧的介绍)

item[‘magnet’]是用来储存abstract中识别到的磁力链接的

后面两个有关image的item,一个用来储存图像的连接便于下载图像,一个是图像的类型,用作储存的时候的文件后缀名

 

 

再来看llss_start.py

#!/usr/bin/env python3
#-*-coding:utf-8-*-

import scrapy
import os
import re
import pipelines
from llss_items import LlssAllItem
from twisted.internet import reactor, defer
from scrapy.crawler import CrawlerRunner
from scrapy.utils.log import configure_logging
import fake_useragent
import copy

ua = fake_useragent.UserAgent()


'''
defination of the two spiders
the first is used to get the url to start with
the second is the main one
'''
class llss_all_pre_spider(scrapy.Spider):
    name = 'llss_all_pre'
    start_urls = [
        'https://www.llss.fun/wp/',
    ]
    def parse(self,response):
        page = response.xpath('//h1[@class="entry-title"]/a/@href').extract_first()
        path = './config/start_urls.txt'
        with open(path,'w') as f:
            f.write(page)


class llss_all_spider(scrapy.Spider):
    name = 'llss_all'
    custom_settings = {
        'TELNETCONSOLE_ENABLED' : False,
        'USER_AGENT': ua.random,
        'COOKIES_ENABLED' : False,
        'ITEM_PIPELINES': {
            'pipelines.LlssAll_abstrct_Pipeline': 1,
            'pipelines.LlssAll_image_Pipeline': 2,
            },
        }
    S = []
    if os.path.isfile('./config/start_urls.txt'):
        with open('./config/start_urls.txt') as file:
            S.append(file.read())
    start_urls = S

    def date_check(self, date_to_check, date_stop):
        if date_to_check < date_stop:
            return 0
        else:
            return 1

    def parse(self,response):
        date_stop = ''
        with open('./config/date_stop.txt') as f:
            t = f.readline()
            date_stop = t[0:4] + t[5:7] + t[8:10]
            date_stop = int(date_stop)
        item = LlssAllItem()
        for content in response.xpath('//body'):
            item['title'] = content.xpath('//h1[@class="entry-title"]/text()').extract_first()
            tmp_date = content.xpath('//body//time[@class="entry-date"]/@datetime').extract_first()
            date = tmp_date[0:4] + tmp_date[5:7] + tmp_date[8:10]
            item['post_date'] = int(date)
            item['abstract'] = (content.xpath('//div[@class="entry-content"]/*/text()|'
                                              '//div[@class="entry-content"]/*/*/text()').extract())
            M1 = (content.xpath('//div[@class="entry-content"]/*/text()|//div[@class="entry-content"]/*/*/'
                                'text()').re('([0-9a-fA-F]+)本站不提供下载([0-9a-fA-F]+)'))
            M1 += (content.xpath('//div[@class="entry-content"]/*/*/*/*/text()|//div[@class="entry-content"]/*/*/*/'
                                'text()').re('([0-9a-fA-F]+)本站不提供下载([0-9a-fA-F]+)'))
            M2 = (content.xpath('//div[@class="entry-content"]/*/text()|'
                                '//div[@class="entry-content"]/*/*/text()').re('.*[0-9a-zA-Z]{15,}'))
            M2 += (content.xpath('//div[@class="entry-content"]/*/*/*/text()|'
                                '//div[@class="entry-content"]/*/*/*/*/text()').re('.*[0-9a-zA-Z]{15,}'))
            M3 = (content.xpath('//div[@class="entry-content"]/*/text()'
                                '|//div[@class="entry-content"]/*/*/text()').re('magnet:\?xt=urn:btih:[0-9a-fA-F]+'))
            M3 += (content.xpath('//div[@class="entry-content"]/*/*/*/text()'
                                '|//div[@class="entry-content"]/*/*/*/*/text()').re('magnet:\?xt=urn:btih:[0-9a-fA-F]+'))
            i = 0
            while i < len(M1):
                tmp=M[i]+M[i+1]
                M2.append(copy.copy(tmp))
                tmp.clear()
                i+=2
            item['magnet_without_prefix'] = M2
            item['magnet_with_prefix'] = M3
            item['image_urls'] = content.xpath('//div[@class="entry-content"]//img/@src').extract()
            image_type = []
            for url in item['image_urls']:
                for i in range(1, 7):
                    if url[-i] == '.':
                        break
                    i += 1
                image_type.append(url[-i:])
            item['image_type'] = image_type
            next_page = response.xpath('//div[@id="content"]//span[@class="nav-previous"]/a/@href').extract_first()
        print(item['post_date'],'==>',date_stop)
        yield item
        if next_page is not None:
            if self.date_check(item['post_date'], date_stop) == 1:
                yield response.follow(next_page, callback=self.parse)
        return item

if __name__=='__main__':
    print('detecting configure')
    if not os.path.isdir('./config/'):
        print('no configure file,creating one')
        os.mkdir('./config/')
    stop = input('please input the date to stop in format as YYYY/MM/DD: ')
    with open('./config/date_stop.txt','w') as f:
        f.write(stop)

    #run the two spiders sequencely
    configure_logging()
    runner = CrawlerRunner()
    @defer.inlineCallbacks

    def crawl():
        yield runner.crawl(llss_all_pre_spider)
        yield runner.crawl(llss_all_spider)
        reactor.stop()

    crawl()
    reactor.run()

    '''
    pause to check results.
    on unix system python scripts usally run in a terminal,so the is no need to pause.
    '''
    if os.name == 'nt':
        os.system('pause')

这个文件中定义了两个爬虫,一个直接进入首页然后获取最新的文章的url,并传递给第二个爬虫,第二个爬虫从获取到的url开始爬取所有页面,当文章发布时间早于设定的时间的时候,爬虫停止。脚本中url的传递是通过文本文件,实际上是多此一举了,当初这样写是因为最开始的时候这两个爬虫是写在两个不同的project里面的,所以不太好传递数据。而停止日期也是通过文本文件传递的,因为我原本想设计一个GUI出来所以采取了这种方式(然而并没有摸鱼摸出GUI)。由于第二个爬虫依赖于第一个爬虫的结果,这就要求爬虫必须按照先后顺序运行而不是同时运行,当然在官方文档中的某一页也提到了如何操作的。

这个脚本在运行时首先检测有没有存放配置文件的文件夹,如果没有就创建一个。然后提示输入停止日期。接着开始按照先后顺序运行爬虫,第一个爬虫没有使用items,而是直接存储了获取的start_urls。第二个爬虫读取start_urls并以list形式复制给start_urls变量(注意必须是list),并对response的内容按照预设规则抽取并赋值给item,利用文章中的”上一页”按钮获取上一篇文章的链接,当上一篇文章存在且时间晚于预设的停止时间时,爬取上一篇文章。磁链是按照神社中常常出现的种子hash的模式通过正则匹配的。在对response进行抽取时,使用的是xpath而不是CSS选择器,原因是私以为xpath的功能更强大一些,当然了,用美丽汤(beautifulsoup)也是极好的。脚本中使用到的fake_useragent是一个用来伪装UserAgent的模块,不过貌似神社没有反爬虫机制所以这个东西显得略多余啊。

由于需要使用pipelines对items进行处理,所以需要激活pipeline,即在settings中写明使用的pipeline并标明启动的先后顺序,不过由于这里没有创建project所以也没有单独的settings文件,所以直接在spider类里面通过custom_settings写明。

 

接着pipelines.py中的pipeline会对items的内容进行处理:

#-*-coding:utf-8-*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import os
import requests
import fake_useragent

ua = fake_useragent.UserAgent()

hd ={'USER_AGENT' :ua.random}

class LlssAll_abstrct_Pipeline(object):
    def process_item(self, item, spider):
        print('\ntrying to write results into files\n')
        if not os.path.isdir('./abstract/'):
            os.mkdir('./abstract')
        path = './abstract/abstract_' + '_' + str(item['post_date']) + '_' + item['title'] + '.txt'
        with open(path, 'a', encoding='utf-8') as f:
            f.write('=' * 75)
            f.write('\nmagnet-links:\n')
            for m in item['magnet_without_prefix']:
                if str(m[0:4])!='http':
                    f.write('magnet:?xt=urn:btih:' + str(m) + '\n')
            for m in item['magnet_with_prefix']:
                f.write(str(m) + '\n')
            f.write('='*75+'\nabstract:\n')
            for a in item['abstract']:
                f.write(str(a))
            f.write('='*75+'\n\n')
        print('done\n')
        return item

class LlssAll_image_Pipeline(object):
    def process_item(self, item, spider):
        print('\ntrying to download images:\n')
        if not os.path.isdir('./image/'):
            os.mkdir('./image')
        path = './image/' + '_' + str(item['post_date']) + '_' + item['title'] + '/'
        if not os.path.isdir(path):
            os.mkdir(path)
        for i in range(0,len(item['image_urls'])):
            tmp_path = path + str(i) + str(item['image_type'][i])
            with open(tmp_path,'wb') as f:
                data = requests.get(str(item['image_urls'][i]),timeout = 1,headers = hd)
                f.write(data.content)
        print('done\n')
        return item

一个pipeline用来写入文本,一个用来写入图像,需要注意的地方就是写入的图像由于是二进制文件,所以模式为’wb’

这里下载图像是通过requests这个包来实现的,其实在scrapy的文档中说到它自带了对于image的pipeline,可以下载image,过滤小图,生成缩略图等等功能,然鹅,那个鬼畜API我就没有调用成功过,所以我选择了用requests来下载。下载图像时,keywords参数设置了一个timeout,为了防止某些图像链接失效无法下载而造成的程序卡住的问题。

 

爬完之后大概是这样一个效果吧llss_results

源码下载

好了司机们注意营养(手动滑稽)

发表评论

电子邮件地址不会被公开。 必填项已用*标注