P站(pixiv)爬虫小试——排行榜篇

最近学了一点爬虫,想实战一下,就顺手选了P站作为目标。本篇准备爬取一定时间内所有登上过排行榜的画师的ID。

作者为在校本科生,刚接触爬虫不久,如有错误请批评指教。

所需要引入的包有这些:

import re,requests,time
from urllib.parse import urlencode

首先打开P站,发现P站必须登录才可以进入,所以必须首先解决登录问题。打开开发者工具,先手动登录一次,看看都得post一些什么参数。
P站(pixiv)爬虫小试——排行榜篇_第1张图片
尝试登录一次后可以看出,除了常规的用户名和密码外,P站登录还需要一个名为“post_key”的参数,并且这个参数似乎是随机的。不过经过多次寻找后发现,这个参数似乎是存在登录页面的源码里所以获取这个参数只需要从网页里直接取就可以啦。而且用户名和密码都没有加密,直接post就可以了,省了不少事。
P站(pixiv)爬虫小试——排行榜篇_第2张图片
登录部分的代码如下:

def login(username,password): #登录
	#模拟一下浏览器
	head={   
	    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
	    'Referer':'https://www.pixiv.net/member_illust.php?mode=medium&illust_id=64503210'
	    }
	    
	#用requests的session模块记录登录的cookie
    session = requests.session()

	#首先进入登录页获取post_key,我用的是正则表达式
  	response = session.get('https://accounts.pixiv.net/login?lang=zh')  
    post_key = re.findall('',
                          response.text)[0]
    post_key = re.findall('value=".*?"',post_key)[0]
    post_key = re.sub('value="','',post_key)
    post_key = re.sub('"', '',post_key)
    
    #将传入的参数用字典的形式表示出来,return_to可以去掉
    data = {
        'pixiv_id': username,
        'password': password,
        'return_to': 'https://www.pixiv.net/',
        'post_key': post_key,
    }
    
    #将data post给登录页面,完成登录
    session.post("https://accounts.pixiv.net/login?lang=zh", data=data, headers=head)
    return session

完成登录后我们再进入排行榜。发现排行榜有好几个参数,不同的参数会使排行榜发生变化,有分区(综合、插图等)、模式(今日、本周等)、时间(例如2019年6月12日为"20190612")。
P站(pixiv)爬虫小试——排行榜篇_第3张图片
按照常理来说,这么多图片应当是存在json文件里动态加载出来的,那么我们来观察一下。

打开开发者工具,把网页往下拉,果然出现了一个json文件,打开一看,里面存了一些图片的信息,包括图片的url、作者的id等等信息都有。所以只要我们把这个json文件读取出来就OK了。
P站(pixiv)爬虫小试——排行榜篇_第4张图片
看看这个json的头文件,发现所需要传入的参数有’mode’、 ‘content’、 ‘date’、‘p’、 ‘format’,其中 'format’是确定的,不用多管。其他的几个分别对应排行榜的模式、分区、页数、分区。除了“页数”(‘p’)的参数我们上面都已经提到过了,分别对应排行榜的模式、分区、时间。而这个“页数”参数只要你继续往下拉就会得知这个就是排行榜的页数,只不过P站是采用动态加载的方法,不用我们自己去点罢了。
P站(pixiv)爬虫小试——排行榜篇_第5张图片
那么事情已经很明朗了,我们只需要通过这几个参数获取带有排行榜图片信息的json文件并解析出来就成功了。

那首先,我们先写一个可以设置排行榜分区等参数的函数,代码如下:

def set_leaderboard(content,mode,R18,date): #选择进入的排行榜
    #content:排行榜的分区,包括综合、插画、动图、漫画,参数为string类型
    #mode:排行榜的模式,包括今日、本周、新人、受男性欢迎、受女性欢迎,参数为string类型
    #R18:是否进入R18模式,包括True、Flase,参数为布尔型
    #date:排行榜具体时间,如2019年6月12日为"20190612",参数为string类型
    #注:不是所有分区都有所有的排行榜模式,如动图区只有今日和本周两个模式
    #建议去P站确定各参数后再调用
    
    print("已设置起始榜单为" + date[:4]+"年"+date[4:6]+"月"+date[6:8]+"日"+content + "区" + mode + "榜")
    
    if content== '综合':
        content = ''
    elif content == '插画':
        content = 'illust'
    elif content == '动图':
        content = 'ugoira'
    elif content == "漫画":
        content ='manga'

    if mode == '今日':
        mode = 'daily'
    elif mode == '本周':
        mode= 'weekly'
    elif mode == '本月':
        mode= 'monthly'
    elif mode == '新人':
        mode = 'rookie'
    elif mode == '受男性欢迎':
        mode = 'male'
    elif mode == '受女性欢迎':
        mode = 'female'

    if R18:
        mode = mode + '_r18'
    else:
        mode = mode + ''
        
    #最终返回一个参数列表,方便接下来加载的时候调用
    return [content,mode,date]

得到需要传入的参数后我们就要写一个获取并解析json文件的函数,代码如下:

def load_leaderboard(username,password,set_leaderboard):#进入榜单,返回一个带有图片url等信息的字典
    #使用前确保set_leaderboard()函数已调用,并将其返回的参数列表传入该函数
    response_list = []
    
    #先登录用户
    session = login(username, password)
    
    #初始化爬取的页数以及所需传入的参数
    p = 1
    data = {
        'mode': set_leaderboard[1],   #这些是 set_leaderboard()函数返回的参数
        'content': set_leaderboard[0],
        'date': set_leaderboard[2],
        'p': p,
        'format': 'json'
    }
    print("正在加载"+"https://www.pixiv.net/ranking.php?" + urlencode(data))
    
    #如果date是今天,需要去除date项;如果content为综合,需要去除content项。
    #这是因为P站排行榜的今日榜不需要传入'date',而综合区不需要传入'content'
    if set_leaderboard[2] == time.strftime("%Y%m%d"):
        data.pop('date')
    if set_leaderboard[0] == '':
        data.pop('content')
        
    #开启循环进行翻页
    while True:
    
    	#翻页并更新data中的'p'参数
        data['p'] = p
        p = p + 1
        
        #使用urlencode()函数将data传入url,获取目标文件
        url = "https://www.pixiv.net/ranking.php?" + urlencode(data)
        response = session.get(url)
        
        #处理的到的文件并转为字典形式
        #不加以下这些会报错,似乎是因为eval()不能处理布尔型数据
        global false, null, true
        false = 'False'
        null = 'None'
        true = 'True'
        try:
            response = eval(response.content)['contents']
        except KeyError:
            break
        response_list = response_list + response
        
    #返回一个列表,列表元素为字典形式,包括了图片的各个信息
    return response_list

以上这个函数返回的是一个列表,其中的元素是个字典,每个元素代表一张图片的各种信息,元素大概长这样:

{'title': '錆色の展望台', 'date': '2019年07月28日 00:52', 'tags': ['背景', '風景', 'オリジナル', '廃墟', '夏', '錆', '入道雲', '夏空', 'オリジナル5000users入り'], 'url': 'https:\\/\\/i.pximg.net\\/c\\/240x480\\/img-master\\/img\\/2019\\/07\\/28\\/00\\/52\\/21\\/75943001_p0_master1200.jpg', 'illust_type': '0', 'illust_book_style': '0', 'illust_page_count': '1', 'user_name': 'mocha@3日目西-O01a', 'profile_img': 'https:\\/\\/i.pximg.net\\/user-profile\\/img\\/2016\\/11\\/28\\/18\\/38\\/41\\/11807428_38b7d5ff662fd4b00c25ed608332a0e9_50.jpg', 'illust_content_type': {'sexual': 0, 'lo': 'False', 'grotesque': 'False', 'violent': 'False', 'homosexual': 'False', 'drug': 'False', 'thoughts': 'False', 'antisocial': 'False', 'religion': 'False', 'original': 'True', 'furry': 'False', 'bl': 'False', 'yuri': 'False'}

所以只要得到了这个元素,不管你是想要图片本身,还是它的作者、tag,这些都可以迎刃而解。由于本次的目标是获取作者的id,所以只要从字典里提取就可以啦。

不过在进行提取之前,我们还得写一些函数,使得排行榜的日期能变化,这样就可以获得好几天的信息了。由于代码很简单,就不过多解释了。

代码如下:

def leaderboard_turn_next_page(set_leaderboard): #排行榜前一天,传入set_leaderboard()函数返回的列表
    date = int(set_leaderboard[2])
    date = date - 1
    set_leaderboard[2] = date
    return set_leaderboard #返回的是一个列表

def leaderboard_turn_previous_page(set_leaderboard): #排行榜后一天
    date = int(set_leaderboard[2])
    date = date + 1
    set_leaderboard[2] = date
    return set_leaderboard

def get_author_id(response_list): #从response_list中得到作者的ID
    author_id_list = []
    for element in response_list:
        author_id_list.append(str(element['user_id']))
    return author_id_list

最后的最后,就是实现这些函数了,代码如下:

if __name__ == '__main__':
    set = set_leaderboard('插画', '今日', False, '20190729') #设置起始页,这里指的是插画区、今日榜、非R18、2019年7月29日
    response_list = load_leaderboard('你的用户名', '你的密码', set)  #中文部分需要你自己填
    id_list = get_author_id(response_list)
    page = 1
    while page <= 2: #获得前()天的数据,获得几天就填几,如果填2就是今天、昨天与前天
        set = leaderboard_turn_next_page(set) #前一天,也可以改成后一天,不过起始页就得改了
        response_list = load_leaderboard('你的用户名', '你的密码', set)
        id_list = id_list + get_author_id(response_list)
        id_list = list({}.fromkeys(id_list).keys()) #列表去重,合并一次去一次重
        page = page + 1
    print(id_list)
    print(len(id_list))

这是我运行上面代码得到信息的一小段:

['648285', '3869665', '11246082', '11932846', '2369321', '2388857', '967677', '1878082']

只要把我所有代码块里的代码复制粘贴在一个py文件里,并引入文章开头的包,同时在代码实现部分填好你的用户名和密码,应该就可以顺利运行了。

如果要改排行榜,在实现部分更改set_leaderboard()函数的参数就可以了。不过改之前要先去P站看看,因为有些参数的组合是不存在的,例如set_leaderboard(‘插画’, ‘受男性欢迎’, False, ‘20190729’) 。当然你设置对了也不一定能跑动,因为除了我代码里的组合我还没试过其他组合,所以不知道会不会出现其他奇怪的错误。(再次手动滑稽)

代码里我也没有设置更改IP的机制,所以大量爬取可能会被封IP,这个以后再说吧。

代码已上传至github:https://github.com/fandaosi/PIXIV_spider。

你可能感兴趣的:(P站(pixiv)爬虫小试——排行榜篇)