python爬取p站排行榜并自动发送邮件

一、前言

       pixiv网站是一个以插图、漫画和小说艺术为中心的虚拟社区网站。其初衷是为全球艺术家提供一个平台,发表他们作品,并透过评级系统反映用户意见。该网站以用户投稿的原创图画为中心,辅以标签、书签、作品回应、排行榜等功能形成具有其特色的社交网络。

       本文以pixiv站作为数据爬取对象,这里简称p站。本文工作内容主要可分为以下内容:

      编写本文是因为我在爬取大量插画后,希望把它发给手机端更换头像,以及想分享好看的图给好友(不得不说,p站的图确实不少,而且原图真的超清,之前我还想在哪里找好看又高清的电脑壁纸)。但是每次都需要自己手动压缩,然后再一个个地发送压缩包,难免觉得繁琐,因此想着用通过python实现自动压缩插画文件然后邮件发送。

二、爬取网页

       由前文《从0实现python批量爬取p站插画》可知,首先一个简单的爬取网页流程大致地分为三个部分,分别是指定请求url、设置headers、发起访问请求。对p站月排行榜的插画网页部分进行数据爬取,首先已知月排行榜插画部分的网页地址,然后通过右键浏览器页面或者按F12进入网页inspect,查看network下面的XHR消息获取个人user-agent。代码实现如下:

import requests

if __name__ == "__main__":
    # 指定url
    month_rank_url = 'https://www.pixiv.net/ranking.php?mode=monthly&content=illust'
    # 设置headers
    rank_url_headers = {
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'User-Agent':'你的user-agent',
    }

    # 发起请求
    rank_res_data = requests.get(month_rank_url, headers=rank_url_headers)
    rank_res_code = rank_res_data.status_code

    # 如果请求成功,那么保存网页
    if rank_res_code == 200:
        with open('./rank.html', 'w', encoding='utf-8') as fp:
            fp.write(rank_res_data.text)
            print('rank.html下载成功!')
    # 否则
    else:
        msg = "请求失败!状态码为:"+str(rank_res_code)
        print(msg)

       运行成功后,可以看到目录下生成rank.html文件,打开显示如下内容:

              由上图可知排行榜的数据可以直接进行访问得到。进入到rank.html文件保存的文件夹,选择浏览器打开rank.html文件,显示p站的插画排行榜,可知p站月排行榜插画部分的网页内容爬取成功!

二、解析并构造数据

1、网页解析

       以上爬取p站月排行榜插画网页内容并下载到本地并不是本文的目的,而是开始批量爬取插画的第一步。要批量爬取插画,首先要对网页本身进行分析并对爬取的网页进行解析。使用浏览器打开下载的rank.html,然后按F12键或者右键第一章图片选择inspect检查网页,可显示如下图信息:

       由上图可知,所有的图片都存放在一个class类名为`ranking-items-container`的div标签内。每个图片通过一个section标签作为一个独立的部分存放在div内,section标签包含了该作品id、排名、标题、作者名等信息,里面嵌入的img标签包含了该作品的缩略图地址(注意了!!!这是构建插画原图地址的关键部分)、作者id等信息。这些信息构成了所要爬取插画的重要线索,因此需要对爬取的网页进行解析,获取到包含插画信息的所有section标签。

       网页的解析主要有三种方式,分别是正则表达式、beautiful soup、xpath,详细介绍与使用可以参照《python爬虫入门总结》。xpath具有较强的通用性与高效性,这里通过该方式对网页进行解析,关键代码如下:

# 如果请求成功,那么对网页进行解析
if rank_res_code == 200:
    # 获取网页原始内容
    rank_res_text = rank_res_data.text
    # 解析网页
    rank_res_etree = etree.HTML(rank_res_text)
    # 获取子标签
    rank_item_section = rank_res_etree.xpath('//div[@class="ranking-items-container"]/div/section')

2、构造数据       

       通过上述对网页进行分析,img标签里面的data-src属性的内容为插画的缩略图地址,该地址包含了原图地址的唯一的特殊子串“2020/0827/00/00/09/83955373”,通过该子串参与字符串的拼接可获得插画的原图地址。

       由于插画原图作为p站的静态资源,网站设置了referer参数,只允许访问自己的静态资源,因此在请求插画原图过程中需要设置referer参数。经过分析,插画的id参与该参数的构成,因此除了缩略图地址,还需要获取插画的id,section标签重重地`data-id`便是该插画的id。

       另外,为方便存储更多与该图相关的信息,获取该插画的标题作为图片文件存储的名字,标题信息位于section标签中的属性`data-title`中。下面,通过设置三个列表origin_url_list、origin_title_list、origin_id_list,分别保存插画原图的地址、标题以及id数据。

      具体的代码实现如下:

# 如果请求成功
origin_url_list = []
origin_title_list = []
origin_id_list = []
if rank_res_code == 200:
    # 获取网页原始内容
    rank_res_text = rank_res_data.text
    # 解析网页
    rank_res_etree = etree.HTML(rank_res_text)
    # 获取子标签
    rank_item_section = rank_res_etree.xpath('//div[@class="ranking-items-container"]/div/section')
    for section in rank_item_section:
        # 获取标题
        origin_title = section.xpath('./@data-title')[0]
        origin_title_list.append(origin_title)
        # 获取id
        origin_id = section.xpath('./@data-id')[0]
        origin_id_list.append(origin_id)
        # 获取缩略图
        thumbnail_url = section.xpath('.//div[@class="_layout-thumbnail"]/img/@data-src')[0]
        # 通过正则表达式截取特殊部分
        origin_url_special = re.findall('img/(.*?)_p0',thumbnail_url)[0]
        # 拼接原图url并添加到列表中
        origin_url = "https://i.pximg.net/img-original/img/"+origin_url_special+"_p0.jpg"
        origin_url_list.append(origin_url)
    
    # 检查数据构造是否正确
    i = -1
    for url in origin_url_list:
        i = i+1
        print(url)
        print(origin_title_list[i])
        print(origin_id_list[i])
        print()
# 否则
else:
    msg = "请求失败!状态码为:"+str(rank_res_code)
    print(msg)

      结果如下图所示,说明数据已准备好!

三、批量爬取原图

      批量爬取的核心还是数据爬取,参照上述提到的模板即可。已经获得所有的插画原图数据,那么在爬取一条数据的基础上增加一个循环操作便可以实现批量爬取所有的图片数据。代码实现如下:

# 如果文件夹“month_rank”不存在,那么创建
month_rank_dir = './month_rank'
if not os.path.exists(month_rank_dir):
    os.mkdir(month_rank_dir)

i = -1
for url in origin_url_list:
    i = i+1
    origin_url_headers = {
        'referer': 'https://www.pixiv.net/artworks/%s' % origin_id_list[i],
        'User-Agent':'你的user-agent',
    }
    origin_res = requests.get(url, headers=origin_url_headers)

    # 如果请求成功,那么存储图片
    origin_res_code = origin_res.status_code
    if origin_res_code == 200:
        origin_img_name = origin_title_list[i]+".png"
        with open(month_rank_dir+"/"+origin_img_name, 'wb') as fp:
            fp.write(origin_res.content)
            msg = origin_img_name+"下载成功!"
            print(msg)
    elif origin_res_code == 404:
            # 重新构建图片url
            # 截取.png之前的部分
            prefix_origin_url = re.findall('(.*?).png', url)[0]
            # 拼接新的url
            new_origin_url = prefix_origin_url+".jpg"
            # 重新添加url、title、id
            origin_url_list.append(new_origin_url)
            origin_title_list.append(origin_title_list[i])
            origin_id_list.append(origin_id_list[i])
    # 否则打印状态码
    else:
        msg = "请求 图片"+origin_title_list[i+1]+" 失败!状态码为:"+str(origin_res_code)
        print(msg)

      运行成功后,生成month_rank文件夹,并保存爬取的所有图片数据,如下图所示。至此,排行榜的插画作品原图爬取成功!(超高清~可以用来作电脑桌面、头像什么的~)

四、压缩插画目录

       有时候,对于下载的这些插画可能想分享给其他人,或者发送到其他设备。比如使用电脑保存了这些图片文件,可以换作电脑壁纸等等,如果想把下载的这些插画换作手机壁纸或者微信头像等等,那么可以通过微信的方式发送到手机端。但是过程中不可以发送整个文件夹,所以要么选择一张一张地发送,要么就是将文件夹打包压缩,这样可以简单方便地整体迁移到另一个设备。总之,通过文件压缩的方式可以很方便地进行数据迁移与分享等等。

       该部分主要涉及到文件压缩功能。首先需要导入提供压缩功能的模块zipfile,然后指定压缩的路径,进行压缩操作。这里主要用到zipfile模块的ZipFile方法。具体代码实现如下:

if os.path.exists(month_rank_dir):
    print('正在为您压缩...')
    # 压缩后的名字
    zip_file_new = month_rank_dir+'.zip'
    zip = zipfile.ZipFile(zip_file_new, 'w', zipfile.ZIP_DEFLATED)
    
    for dir_path, dir_names, file_names in os.walk(month_rank_dir):
        # 去掉目标跟路径,只对目标文件夹下面的文件及文件夹进行压缩
        fpath = dir_path.replace(month_rank_dir, '')
        for filename in file_names:
            zip.write(os.path.join(dir_path, filename), os.path.join(fpath, filename))
    zip.close()
    print('该目录压缩成功!')
else:
    print('您要压缩的目录不存在...')

      运行成功在,如下图所示在指定目录下生成.zip压缩文件。打包压缩文件夹可以对文件很便捷地统一发送管理,但是这里爬取的图片都是比较高清的原图,一个图片文件就可能达到2M的大小,这样以来,整个压缩文件就比较大,在传输的过程中需要耗费更长的时间。另外,在后文中使用邮件进行附件发送,一般来说邮件服务器会对邮件大小作一定的要求,如果附件超过一定的大小,那么将会发送失败。因此,一个较好的方式是,将一个文件夹压缩成多个压缩文件,一次发送或者上传多个文件,以确保发送的文件尺寸在规定范围内,该方法将在后续进行整理。

五、发送插画邮件

       经过以上的批量爬取以及压缩的工作,在本地可以得到一个包含所有爬取插画原图的压缩包文件。这时,通过邮箱的方式,可以在手极端打开邮箱下载插画压缩文件,也可以通过邮件方式发送给多个好友,从而可以实现多设备、多用户之间共享。

       该部分qq邮箱服务器进行邮件发送服务。要想使用该服务,需要获取本人邮箱的授权码,获取方式为:qq邮箱首页-->设置-->账户-->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务-->开启POP3/SMTP服务-->获取授权码,然后保存该授权码,在发送邮件时需要用到。重点关注如下图:

       在发送插画邮件部分,主要用到smtplib以及email两个第三方库。首先需要导包。

import smtplib                                   # smtplib 用于邮件的发信动作
from email.mime.text import MIMEText             # email 用于构建邮件内容
from email.header import Header                  # 用于构建邮件头
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication

       然后需要指定发送者的邮箱以及授权码,接收者的邮箱列表(如果列表只有一个那么就发送给一个人,如果有多个就实现了同时给多人发送邮件),附件的绝对路径以及要使用的邮件服务器,这里用到的是qq邮箱服务器。

print('开始发送邮件...')
# 发送者的邮箱和授权码
from_addr = '你的邮箱'
password = '你的邮箱授权码'
# 接收者的邮箱及名称
to_addr = ['对方邮箱1', '对方邮箱2', ...]
# 附件
to_attachment = '插画压缩包的绝对路径'

# 发信服务器
smtp_server = 'smtp.qq.com'

       接下来的工作便是邮件的主体部分:正文和附件。在这一部分,需要创建一个带附件的实例,可以对邮件的的信息头进行设置,比如发送者名字、接收者名字以及邮件主题。其次添加邮件正文,比如你有什么想对接收者说的或者有什么需要注明的。最后创建一个MIMEApplication实例读取要发送的附件并添加到邮件主体中。代码如下:

# 创建一个带附件的实例
msg = MIMEMultipart()
msg['From'] = Header('你的邮箱头(可自行设置)', 'utf-8')    
msg['To'] = Header(";".join(to_addr), 'utf-8')
msg['Subject'] = Header('您要的P站排行榜来咯', 'utf-8')

# 邮件正文内容
text = MIMEText('hello!附件是p站的9月排行榜,请注意查收~', 'plain', 'utf-8')
msg.attach(text)

# 邮件附件
att = MIMEApplication(open(to_attachment, 'rb').read())
# 修改附件名称
att.add_header('Content-Disposition', 'attachment', filename="p站排行榜插画.zip")
msg.attach(att)

       最后,开始发送邮件。大致分为以下步骤:

  • 连接服务器
  • 登录邮箱
  • 发送邮件
  • 关闭服务器

      代码如下:

# 开启发信服务,这里使用的是加密传输
server = smtplib.SMTP_SSL(smtp_server)
server.connect(smtp_server,465)
# 登录发信邮箱
server.login(from_addr, password)
# 发送邮件
server.sendmail(from_addr, to_addr, msg.as_string())
# 关闭服务器
server.quit()

print('邮件发送成功...')

六、完整python实现

       在理解上面这些之后,在下面代码的基础上,可以把其中涉及到的文件操作以及邮件操作等部分都可以进行封装,然后通过函数调用,这样的方式应该更为通用。另外对于邮件的接收者、正文、附件等等,可以通过列表对象存储,然后通过参数传递的方式进行邮件发送。自行解决hhh~~

import requests
import pprint
from lxml import etree
import re
import os
import time
import zipfile
import smtplib                                   # smtplib 用于邮件的发信动作
from email.mime.text import MIMEText             # email 用于构建邮件内容
from email.header import Header                  # 用于构建邮件头
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication


if __name__ == "__main__":
    # step 1 : 获取网页

    month_rank_url = 'https://www.pixiv.net/ranking.php?mode=monthly&content=illust'
    rank_url_headers = {
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'User-Agent':'你的user-agent',
    }
    rank_res_data = requests.get(month_rank_url, headers=rank_url_headers)
    rank_res_code = rank_res_data.status_code

    origin_url_list = []
    origin_title_list = []
    origin_id_list = []
    if rank_res_code == 200:
        # step 2 : 解析网页
        
        # 获取网页原始内容
        rank_res_text = rank_res_data.text
        rank_res_etree = etree.HTML(rank_res_text)
        # 网页解析
        rank_item_section = rank_res_etree.xpath('//div[@class="ranking-items-container"]/div/section')
        for section in rank_item_section:
            origin_title = section.xpath('./@data-title')[0]
            origin_title_list.append(origin_title)
            origin_id = section.xpath('./@data-id')[0]
            origin_id_list.append(origin_id)
            thumbnail_url = section.xpath('.//div[@class="_layout-thumbnail"]/img/@data-src')[0]
            origin_url_special = re.findall('img/(.*?)_p0',thumbnail_url)[0]
            origin_url = "https://i.pximg.net/img-original/img/"+origin_url_special+"_p0.jpg"
            origin_url_list.append(origin_url)

        # step 3 : 下载图片

        # 如果文件夹“month_rank”不存在,那么创建
        month_rank_dir_name = 'month_rank_new'
        month_rank_dir = './'+month_rank_dir_name
        if not os.path.exists(month_rank_dir):
            os.mkdir(month_rank_dir)

        i = -1
        for url in origin_url_list:
            i = i+1
            origin_url_headers = {
                'referer': 'https://www.pixiv.net/artworks/%s' % origin_id_list[i],
                'User-Agent':'你的user-agent',
            }
            origin_res = requests.get(url, headers=origin_url_headers)

            # 如果请求成功,那么存储图片
            origin_res_code = origin_res.status_code
            if origin_res_code == 200:
                origin_img_name = origin_title_list[i]+".png"
                with open(month_rank_dir+"/"+origin_img_name, 'wb') as fp:
                    fp.write(origin_res.content)
                    msg = origin_img_name+"下载成功!"
                    print(msg)
            elif origin_res_code == 404:
                # 重新构建图片url
                prefix_origin_url = re.findall('(.*?).png', url)[0]
                new_origin_url = prefix_origin_url+".jpg"
                # 重新添加url、title、id
                origin_url_list.append(new_origin_url)
                origin_title_list.append(origin_title_list[i])
                origin_id_list.append(origin_id_list[i])
            else:
                msg = "请求 图片"+origin_title_list[i]+" 失败!状态码为:"+str(origin_res_code)
                print(msg)
    # 否则
    else:
        msg = "请求失败!状态码为:"+str(rank_res_code)
        print(msg)

    # step 4 : 压缩文件夹

    zip_file_new = month_rank_dir+'.zip'
    if os.path.exists(month_rank_dir):
        print('正在为您压缩...')
        # 压缩后的名字
        zip = zipfile.ZipFile(zip_file_new, 'w', zipfile.ZIP_DEFLATED)
        
        for dir_path, dir_names, file_names in os.walk(month_rank_dir):
            fpath = dir_path.replace(month_rank_dir, '')
            for filename in file_names:
                zip.write(os.path.join(dir_path, filename), os.path.join(fpath, filename))
        zip.close()
        print('该目录压缩成功!')
    else:
        print('您要压缩的目录不存在...')

    # step 5 : 发送邮件

    print('开始发送邮件...')
    # 发送者的邮箱和授权码
    from_addr = '你的邮箱'
    password = '你的授权码'
    # 接收者的邮箱及名称
    to_addr = ['[email protected]', '[email protected]', '[email protected]', ...]
    to_attachment = '刚刚生成压缩文件的绝对路径'
    smtp_server = 'smtp.qq.com'

    # 创建一个带附件的实例
    msg = MIMEMultipart()
    msg['From'] = Header('星河Cynthia <星河[email protected]>', 'utf-8')    
    msg['To'] = ";".join(to_addr)
    msg['Subject'] = Header('您要的P站高清插画来咯', 'utf-8')

    # 邮件正文内容
    text = MIMEText('hello!附件是爬取的p站插画,请注意查收~', 'plain', 'utf-8')
    msg.attach(text)

    # 邮件附件
    att = MIMEApplication(open(to_attachment, 'rb').read())\
    att.add_header('Content-Disposition', 'attachment', filename="p站插画.zip")
    msg.attach(att)

    # 开启发信服务,这里使用的是加密传输
    server = smtplib.SMTP_SSL(smtp_server)
    server.connect(smtp_server,465)
    server.login(from_addr, password)
    server.sendmail(from_addr, to_addr, msg.as_string())
    server.quit()

    print('邮件发送成功...')

      然后接收者将收到下图这样的邮件:

写在最后:

1、本文的部分操作学习参考以下资料:

2、如果有错误欢迎指正~

3、帮我康康左下角的小拇指亮了没~

猜你喜欢

转载自blog.csdn.net/VinWqx/article/details/108772687