Python

爬虫:Scrapy抓取美图录全站数据

微信扫一扫,分享到朋友圈

爬虫:Scrapy抓取美图录全站数据
0

目标网站

目标网站: https://www.meitulu.com/

主要采集日韩、港台、国内三个分类

Python及包版本信息

Python Version:3.7.3

PIP Version:19.2.1

Scrapy Version:1.7.2

PyMysql Version:0.9.3

scrapy_fake_useragent:1.1.0

编码前

scrapy startproject collection_sun # 创建项目

默认配置

settings配置

ROBOTSTXT_OBEY = False # 去掉注释,并修改值为False
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,  # 禁用默认的ua
    'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 1,  # 使用随机ua
    'collection_pics.middlewares.CollectionPicsDownloaderMiddleware': 543,
} # 下载图片处理中间件,此次采集中主要是设置请求头的“Referer”
ITEM_PIPELINES = {
    'collection_pics.pipelines.CollectionPicsPipeline': 300, # 设置默认下载的路径相关信息及下载处理
    'collection_pics.pipelines.CollectionDataPipeline': 301, # 下载完成后存入数据库,用于后期发布(后期发布本文不进行描述)
}

增加一些存储及数据库配置信息

IMAGES_STORE = 'imageDownloads'  # 默认存储位置
IMAGES_URLS_FIELD = 'pics' # 提取图片地址默认字段
IMAGES_EXPIRES = 1 # 指定天数已经下载过的不再下载

WEBSITE = "meitulu" # 变量,编码时会用到
CATEGORY = "rihan" # 变量,编码时会用到

MYSQL_HOST = "127.0.0.1"
MYSQL_PORT = 3306
MYSQL_DB_NAME = "collection"
MYSQL_USER = "root"
MYSQL_PASSWD = "123456"

items

    title = scrapy.Field()  # 标题 目标网站字段
    wid = scrapy.Field()  # 目标网站POST ID 目标网站字段
    category = scrapy.Field()  # 所属分类 采集固定
    tags = scrapy.Field()  # 标签 目标网站字段
    lssuer = scrapy.Field()  # 发布机构 目标网站字段
    lsbn = scrapy.Field()  # 期刊编号 目标网站字段
    pic_number = scrapy.Field()  # 图片数量 目标网站字段
    pic_resolution = scrapy.Field()  # 图片分辨率 目标网站字段
    model_name = scrapy.Field()  # 模特姓名 目标网站字段
    push_date = scrapy.Field() # 发布时间 目标网站字段
    pics = scrapy.Field() # 图片信息,必须
    target_url = scrapy.Field() # 目标路径,主要是便于后期找问题,不是必须

spider

urls = response.xpath('//ul[@class="img"]/li/a/@href').extract() # 获取所有文章地址
for url in urls: # 循环文章地址
    path = os.path.join(os.path.abspath(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
                        IMAGES_STORE,
                        WEBSITE, CATEGORY, url.split("/")[-1].split('.')[0]) # 我这里判断了一下文件夹是否存在,如果存在就不采集了,避免重复采集。如果不需要的话,也可以去掉。(与IMAGES_EXPIRES不冲突,主要是解决认为终端等一些因素,非必要流程)
    if os.path.exists(path): # 如果存在就跳过
        continue
    yield scrapy.Request(url, self.parse_child) # 请求详细地址

next_url = response.xpath('//a[text()="下一页"]/@href').extract_first() #分析是否存在下一页
if next_url: # 如果存在下一页
    yield scrapy.Request(url="https://www.meitulu.com%s" % next_url, callback=self.parse) # 继续往下请求

因为在获取期刊编号等信息的时候,有好多字段是不固定的,所以这里我们用正则的方式去判断一下是哪个字段。

下面的代码可以单写一个处理类来处理,通用代码。(这段代码很次,可以自行优化,不过我写的这个也可以用)

def info_process(target_value):
    pattern1 = re.compile('<a(.*)>([^<]*)</a>')
    cr1 = pattern1.search(target_value)
    if cr1 is not None and len(cr1.groups()) == 2:
        return cr1.group(2)

    pattern2 = re.compile(':([^<]*)')
    cr2 = pattern2.search(target_value)
    if cr2 is not None and len(cr2.groups()) == 1:
        return cr2.group(1)
item = response.meta.get('item', None) # 内容页存在下一页,所以判断下item是否存在meta中
if item is None:  # 以下内容只获取一遍
    item = CollectionPicsItem()
    item['target_url'] = response.url
    item['wid'] = response.url.split("/")[-1].split('.')[0]
    item['title'] = response.xpath('//div[@class="width"]//h1/text()').extract_first()
    meta_pics = None
    item['lssuer'] = None
    item['lsbn'] = None
    item['pic_number'] = 0
    item['pic_resolution'] = None
    item['model_name'] = None
    item['push_date'] = None
else:
    meta_pics = item['pics'] # 主要读取的就是pics

tags = response.xpath('//div[@class="fenxiang_l"]/a/text()').extract() # 取tags
item['tags'] = ",".join(tags) # 把tags拼成字符串

# 获取发行相关
cl = response.xpath('//div[@class="c_l"]/p').extract() # 通过自定义的方法,处理一下以下几个不固定字段
for c in cl:
    c = c.replace(":", ":").replace(" ", "")
    if "发行机构" in c:  # 处理发行机构逻辑
        item['lssuer'] = info_process(c).strip()
        continue
    if "期刊编号" in c:
        item['lsbn'] = info_process(c).strip()
        continue
    if "图片数量" in c:
        item['pic_number'] = info_process(c.replace("张", "")).strip()
        continue
    if "分辨率" in c:
        item['pic_resolution'] = info_process(c).strip()
        continue
    if "模特姓名" in c:
        item['model_name'] = info_process(c).strip()
        continue
    if "发行时间" in c:
        item['push_date'] = info_process(c).strip()
        continue

# meta_pics = response.meta.get('pics', None)
pics = response.xpath('//div[@class="content"]//img/@src').extract() # 获取所有图片
if meta_pics is not None:
    pics = meta_pics + pics # 数组拼接
item['pics'] = pics
next_url = response.xpath('//a[text()="下一页"]/@href').extract_first() #看看下一页是否存在

if next_url: 

    if next_url not in response.url:# 目标网站存在一个问题,就是即使到了下一页,下一页还是可以用的,所以这里我们判断一下,如果下一页和当前页不是一个地址,那么就继续请求,否则就直接返回item
        yield scrapy.Request(url="https://www.meitulu.com%s" % next_url, callback=self.parse_child,
                             meta={'item': item})
    else:
        yield item
else:
    yield item

middlewares

这里我们根据前面settings里面描述的,创建一个 CollectionPicsDownloaderMiddleware 。我们只需要修改process_request方法就可以。

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        request.headers['Referer'] = "https://www.meitulu.com/" # 添加的代码,其他的都是默认代码。

        return None

pipelines

CollectionPicsPipeline类代码

class CollectionPicsPipeline(ImagesPipeline):

    def get_media_requests(self, item, info):
        for url in item['pics']:
            yield Request(url, meta={'item': item})

    def file_path(self, request, response=None, info=None): # 我这里修改了一下默认的存储路径及文件名字
        file_name = request.url.split('/')[-1]
        item = request.meta.get('item')
        return '%s/%s/%s/%s' % (WEBSITE, CATEGORY, item['wid'], file_name)

CollectionDataPipeline类代码(我这里图省事,没有用model)

class CollectionDataPipeline(object):

    def __init__(self):
        self.conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, database=MYSQL_DB_NAME, user=MYSQL_USER,
                                    password=MYSQL_PASSWD, charset="utf8") # 连接数据库

    def process_item(self, item, spider): # 执行插入
        insert_sql = """
        
        insert into  cr_collection (wid,title,tags,pic_resolution,pic_number,model_name,push_date,target_url,lsbn,lssuer,website,weburl,category)  
        values ('%s','%s','%s','%s',%s,'%s','%s','%s','%s','%s','meitulu','https://www.meitulu.com','%s')
        
        """ % (item['wid'], item['title'], item['tags'], item['pic_resolution'], item['pic_number'], item['model_name'],
               item['push_date'], item['target_url'], item['lsbn'], item['lssuer'], CATEGORY)

        cursor = self.conn.cursor()
        cursor.execute(insert_sql)
        self.conn.commit()

这样,初始化采集我们就做完了。

一个后端程序员,却非要干点前端的事。攻城已有十年,暂未留下任何印记。

Yii2环境搭建

上一篇

Maven配置及国内镜像源配置

下一篇

你也可能喜欢

发表评论

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

提示:点击验证后方可评论!

插入图片
爬虫:Scrapy抓取美图录全站数据

长按储存图像,分享给朋友