一次python爬虫爬取p站的经历

代码有点乱,等有空了再回来改。

先上完整代码:

import requests
import re
import queue
import threading

#登陆账号和密码
pixiv_id='xxxxx'
password='xxxxxx'

#关键字注意空格和斜杠等符号的url编码,比如我要搜索含有Fate/GrandOrder和リッパー标签的图片,则url的word后面应为:Fate%2FGrandOrder%20AND%20(%20リッパー%20%20)
#按旧排序:&order=date,默认无
#标签完全相似:s_tag_full,部分相似:s_tag
#尺寸:横长&ratio=0.5,长度&ratio=-0.5,正方形&ratio=0

#保存在本地的路径
local_path='F:/玛修/'
#这里设置关键字,在‘word=’的后面,注意要进行url编码
url_index='http://www.pixiv.net/search.php?s_mode=s_tag&word=マシュ&p='

#以下三个条件只要满足一个条件就会爬取下来
#爬取图片的获赞数应达到多少
min_score=1500
#爬取图片的获赞率达到的10%的获赞数应达到多少
min_score1=1000
#爬取图片的获赞率达到的30%的获赞数应达到多少
min_score2=500

headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
    'Referer': 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'
    }

login_url='https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'
post_url='https://accounts.pixiv.net/api/login?lang=zh'
se=requests.Session()

referer='http://www.pixiv.net'
url_img='http://www.pixiv.net/member_illust.php?mode=medium&illust_id='
url_manga='http://www.pixiv.net/member_illust.php?mode=manga&illust_id='
url_manga_big1='http://www.pixiv.net/member_illust.php?mode=manga_big&illust_id='
url_manga_big2='&page='

class P(threading.Thread):
    def __init__(self,qu1,qu2):
        threading.Thread.__init__(self)
        self._queue1=qu1
        self._queue2=qu2
        
    def run(self):
        while not self._queue1.empty():
            self.get_img_url(self._queue1.get(),self._queue2)
        while not self._queue2.empty():
            self.spider(self._queue2.get())
            
    def spider(self,num):
        url=url_img+num
        count1=0
        headers['Referer']=referer
        while True:
            try:
                count1=count1+1
                #获取图片信息介绍页面
                img_page=se.get(url=url,headers=headers).content.decode('utf-8')
                people=re.findall(re.compile('view-count">(\d+)'), img_page)
                score=re.findall(re.compile('rated-count">(\d+)'), img_page)
                avar=0.1
                if float(str(people[0]))>0:
                    avar=float(str(score[0]))/float(str(people[0]))
                if (float(str(score[0]))>=min_score) or (float(str(score[0]))>=min_score1 and avar>=0.1) or (float(str(score[0]))>=min_score2 and avar==0.3):
                    multiple=re.findall(re.compile('
'),img_page) count2=0 if len(multiple): manga_info_url=url_manga+num headers['Referer']=url while True: try: count2=count2+1 #获取manga类型图片的套图页面 manga_page=se.get(url=manga_info_url,headers=headers).content.decode('utf-8') manga_urls=re.findall(re.compile('data-src="(.*?)"'),manga_page) #不要manga类型的大于5张的图片 if len(manga_urls)>=5: return for i in range(len(manga_urls)): manga_urls[i]=re.sub('_master\d+', '', manga_urls[i]) manga_urls[i]=re.sub('master', 'original', manga_urls[i]) headers['Referer']=manga_info_url for each_manga in manga_urls: with open(local_path+each_manga.split('/')[-1],'bw') as file: img=se.get(each_manga,headers=headers) if str(img.status_code)=='404': each_manga=re.sub('.jpg','.png',each_manga) img=se.get(each_manga,headers=headers) count3=0 while not str(img.status_code)=='200' and not count3==3: count3=count3+1 img=se.get(each_manga,headers=headers) file.write(img.content) print('mangaSuccess!') break except Exception as e: print('mangaError:'+str(e)) if count2==3: break continue else: while True: try: count2=count2+1 img_src=re.findall(re.compile('(https://i.pximg.net/img-original/img.*?)"'),img_page) if not len(img_src): break print('img_src:',img_src[0]) with open(local_path+img_src[0].split('/')[-1],'bw') as file: img=se.get(img_src[0],headers=headers) file.write(img.content) print('mediumSuccess!') break except Exception as e: print('imgError:'+str(e)) if count2==3: break continue else: break break except Exception as e: print(str(e)) if count1==3: break continue def get_img_url(self,url_page,qu2): count=0 while True: try: count=count+1 html_page=se.get(url=url_page,headers=headers) print(html_page.status_code) nums=re.findall(re.compile('data-click-label="(\d+)"'), html_page.content.decode('utf-8'))#data-click-label for i in range(len(nums)): qu2.put(str(nums[i])) print(str(nums[i])) break except Exception as e: print(str(e)) if count==3: break continue def main(): page_login=se.get(url=login_url,headers=headers).content.decode('utf-8') post_key=re.findall(re.compile('post_key" value="(.+?)"'),page_login)[0] print(post_key) data={ 'pixiv_id':pixiv_id, 'password':str(password), 'post_key':post_key, 'source':'pc', 'return_to':'www.pixiv.net' } se.post(post_url,data=data,headers=headers) #例のセーター,胸開きタートルネック qu1=queue.Queue() qu2=queue.Queue() for i in range(1,1001): qu1.put(url_index+str(i)) threads=[] thread_count=100 for i in range(thread_count): threads.append(P(qu1,qu2)) for t in threads: t.start() for t in threads: t.join() if __name__ == '__main__': main()


这个爬虫主要使用了requests、re、queue、threading几个包,运行之前自己设置好各项参数(都在代码最前面)。页数是p站固定的,最多只能爬1000页,所以不用修改。线程数是100个,可以在main函数里修改。接下来简单分析一下就可以,因为代码不长而且并不难。

login_url='https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'
post_url='https://accounts.pixiv.net/api/login?lang=zh'
se=requests.Session()
登陆的url以及提交登陆信息的url。这里用到session来模拟登陆。

headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0',
    'Referer': 'https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index'
    }
反防爬的核心,需要在每个请求中加上该头部。p站的防爬会检测referer,所以我后面的spider()方法里有动态的改变referer的策略,能有效欺骗该防爬机制。

url_img='http://www.pixiv.net/member_illust.php?mode=medium&illust_id='
url_manga='http://www.pixiv.net/member_illust.php?mode=manga&illust_id='
url_manga_big1='http://www.pixiv.net/member_illust.php?mode=manga_big&illust_id='
url_manga_big2='&page='

上面几个全局变量的作用分别是:

url_img:图片的信息页面的主要url;

url_manga:漫画类型的图片的小图展示页面url;

url_manga_big1:漫画类型的大图的url的前部分,配合下面那个的变量使用;

url_manga_big2:漫画类型的大图的url的后部分,配合上面那个的变量使用;

上面的四个变量都需要配合接下来爬取到的图片id进行使用。


class P:

如代码所示,这个类继承了Threading父类,方便我们接下来的多线程爬取提高效率。顺便说一下,这个多线程是配合队列queue使用的,具体在类方法run()里面,队列qu1用来存放所有页数的url,qu2则是用来存放所有爬取到的图片的p站id,可以配合我们的全局变量


run:

很容易理解,先把每一页的图片id取出来放到qu2,然后再用spider()方法爬取。

spider:

本次爬虫的核心部分。

    def spider(self,num):
        url=url_img+num
        count1=0
        headers['Referer']=referer
        while True:
            try:
                count1=count1+1
                #获取图片信息介绍页面
                img_page=se.get(url=url,headers=headers).content.decode('utf-8')
                people=re.findall(re.compile('view-count">(\d+)'), img_page)
                score=re.findall(re.compile('rated-count">(\d+)'), img_page)
                avar=0.1
                if float(str(people[0]))>0:
                    avar=float(str(score[0]))/float(str(people[0]))
                if (float(str(score[0]))>=min_score) or (float(str(score[0]))>=min_score1 and avar>=0.1) or (float(str(score[0]))>=min_score2 and avar==0.3):
                    multiple=re.findall(re.compile('
'),img_page) count2=0 if len(multiple): manga_info_url=url_manga+num headers['Referer']=url while True: try: count2=count2+1 #获取manga类型图片的套图页面 manga_page=se.get(url=manga_info_url,headers=headers).content.decode('utf-8') manga_urls=re.findall(re.compile('data-src="(.*?)"'),manga_page) #不要manga类型的大于5张的图片 if len(manga_urls)>=5: return for i in range(len(manga_urls)): manga_urls[i]=re.sub('_master\d+', '', manga_urls[i]) manga_urls[i]=re.sub('master', 'original', manga_urls[i]) headers['Referer']=manga_info_url for each_manga in manga_urls: with open(local_path+each_manga.split('/')[-1],'bw') as file: img=se.get(each_manga,headers=headers) if str(img.status_code)=='404': each_manga=re.sub('.jpg','.png',each_manga) img=se.get(each_manga,headers=headers) count3=0 while not str(img.status_code)=='200' and not count3==3: count3=count3+1 img=se.get(each_manga,headers=headers) file.write(img.content) print('mangaSuccess!') break except Exception as e: print('mangaError:'+str(e)) if count2==3: break continue else: while True: try: count2=count2+1 img_src=re.findall(re.compile('(https://i.pximg.net/img-original/img.*?)"'),img_page) if not len(img_src): break print('img_src:',img_src[0]) with open(local_path+img_src[0].split('/')[-1],'bw') as file: img=se.get(img_src[0],headers=headers) file.write(img.content) print('mediumSuccess!') break except Exception as e: print('imgError:'+str(e)) if count2==3: break continue else: break break except Exception as e: print(str(e)) if count1==3: break continue
里面的count+数字是重试的计数器,因为有时候加载网页会失败,所以我给每一个页面的加载设置了一个重试最大值为3。其实没什么好说的,都是分析了各个网页的源代码之后选取合适的正则表达式来匹配关键字符串,然后使用关键字符串来进行下一步的筛选条件或者继续爬取。比如:

               people=re.findall(re.compile('view-count">(\d+)'), img_page)
               score=re.findall(re.compile('rated-count">(\d+)'), img_page)
               avar=0.1
               if float(str(people[0]))>0:
                   avar=float(str(score[0]))/float(str(people[0]))
               if (float(str(score[0]))>=min_score) or (float(str(score[0]))>=min_score1 and avar>=0.1) or (float(str(score[0]))>=min_score2 and avar==0.3):
这里就是先匹配爬下来的网页源代码里的浏览人数(people)、点赞次数(score),然后计算点赞率(avar),接下来进行判断。这些都是要自己去看过源代码才比较好理解的,这里就不详细说了,可以自己去p站看源代码。注意在找需要的正则匹配的时候,最好挑选不容易和其他无关的数据混淆的。还有一点是要注意的: P站登陆和没登陆的时候的网页源代码有一定的出入!我之前就因为这个坑调试了半天,还以为是编码的锅。


get_img_url:

顾名思义,就是获取图片的url的,——当然这是本来的功能,后来我改成了只获取图片的id,名字懒得改了,知道意思就可以了。这个方法更简单,可以说是上面那个方法的缩小版。

    def get_img_url(self,url_page,qu2):
        count=0
        while True:
            try:
                count=count+1
                html_page=se.get(url=url_page,headers=headers)
                print(html_page.status_code)
                nums=re.findall(re.compile('data-click-label="(\d+)"'), html_page.content.decode('utf-8'))#data-click-label

                for i in range(len(nums)):
                    qu2.put(str(nums[i]))
                    print(str(nums[i]))
                break
            except Exception as e:
                print(str(e))
                if count==3:
                    break
                continue

main:

主要包括了登陆、添加搜索页面的url、配置线程。

登陆:

    page_login=se.get(url=login_url,headers=headers).content.decode('utf-8')
    post_key=re.findall(re.compile('post_key" value="(.+?)"'),page_login)[0]
    print(post_key)

    data={
        'pixiv_id':pixiv_id,
        'password':str(password),
        'post_key':post_key,
        'source':'pc',
        'return_to':'www.pixiv.net'
        }

    se.post(post_url,data=data,headers=headers)
首先获取登录界面的源代码,然后匹配它的post_key,设置好需要post的各项参数,提交。

关于post_key:

这个是p站的防爬措施之一吧,存在于源代码之中,每次刷新页面都是不一样的,所以需要动态获取。

添加搜索页面url:

这个很简单,略过不提。


配置线程:

    threads=[]
    thread_count=100
    for i in range(thread_count):
        threads.append(P(qu1,qu2))
    
    for t in threads:
        t.start()
        
    for t in threads:
        t.join()
首先创建一个存放多线程的数组,将多线程定义好并放进去;然后逐个开始,最后逐个停止清除。ps:这个join()我也不是很理解,大家可以在网上看看大神写的教程。


最后:

第一次写技术文档,文笔简陋,大家海涵。写爬虫重要的是去分析目标网站的那个过程,因为很可能每个人爬取的思路都不一样。


顺便上传一下成果图:

一次python爬虫爬取p站的经历_第1张图片一次python爬虫爬取p站的经历_第2张图片

你可能感兴趣的:(python)