avatar

目录
Scrapy爬取p站热门插图

寒假闲来无聊,又正好在学Scrapy,于是便爬个p站来玩玩。在爬的过程中,总的来说p站反爬措施不强,貌似只有防盗链的存在,连模拟登陆都不需要的样子。但还是在其中遇到了许多坑,于是就记录下来了

分析

p站的加载采取的是异步ajax,分析一下接口,如图
很容易批量生成url,且这些接口返回的都是json数据,也很容易提取其中的数据,如图

具体参数如下,p参数为请求的页数,但参数tt似乎并没有什么卵用

里面的json数据

图上所示的url是240x480或同规格的缩略图,如果想要爬取原尺寸图片,则还需要对原尺寸图片的url进行分析,索性格式大致一样,只有后面的后缀名会有些许差别,跟缩略图的url也大体相近,如下所示

https://i.pximg.net/img-original/img/2019/02/21/01/22/24/(作品id)_p0(.png)或(.jpg)

由于是两个后缀二选一,所以需要自己重写个RetryMiddleware,如当请求.jpg后缀失败的时候改用请求.png后缀

但是如果没做任何措施,爬取图片时会报403,这也就是p站防盗链在作祟,这里p站通过判断referer来作出判断,而referer经过判断,需要来源于作品展示页面,url格式大体相同,即

https://www.pixiv.net/member_illust.php?mode=medium&illust_id=(作品id)

于是给请求头添上referer即可成功爬取图片

开爬

爬json数据url:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def start_requests(self):
SSR_POSITION=self.settings.get('SSR_POSITION')
win32api.ShellExecute(0, 'open', SSR_POSITION, '','',1)
base_url='https://www.pixiv.net/ranking.php?'
params={
'mode': 'daily',
'content': 'illust',
'p':1,
'format': 'json',
}
MAX_PAGE=self.settings.get('MAX_PAGE',5)
for i in range(1,MAX_PAGE+1):
params['p']=i
url=base_url+urlencode(params)
yield scrapy.Request(url,self.parse)

重写ImagesPipeline:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ImagesPivixPipeline(ImagesPipeline):
def get_media_requests(self,item,info):
#没有refer会报403
referer='https://www.pixiv.net/member_illust.php?mode=medium&illust_id='+item['illust_id']
headers={
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
'referer':referer
}
yield Request(item['url'],headers=headers,dont_filter=True)

def item_completed(self,results,item,info):
image_paths=[x['path'] for ok,x in results if ok]
if not image_paths:
raise DropItem
return item

def file_path(self, request, response=None, info=None):
url = request.url
file_name = url.split('/')[-1]
return file_name

重写RetryMiddleware:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
if request.meta.get('dont_retry', False):
return response
if response.status in self.retry_http_codes:
reason = response_status_message(response.status)
part_url=request.url.split('.')
if part_url[-1]=="jpg":
part_url[-1]='png'
else:
part_url[-1]='jpg'
url='.'.join(part_url)
request=request.replace(url=url)
return self._retry(request, reason, spider) or response
return response

遇到的坑

在重写自己的重写RetryMiddleware中间件时,总是不执行自己的MyRetryMiddleware,而是去执行默认的RetryMiddleware,查资料发现在特殊情况下(如请求失败时),会执行默认的Middleware(包括RetryMiddleware),如果值低于550,则逻辑便是先执行RetryMiddleware,再执行自己的RetryMiddleware,但由于默认的的RetryMiddleware会将retry_time用光,致使自己的RetryMiddleware白写。

总结

反爬措施+1:判断referer
重写RetryMiddleware中间件时,需要注意其在settings.py中所设置的值,不然很有可能白写,如果其值低于550(即默认的RetryMiddleware值)。
完整代码请见 github

文章作者: f1rry
文章链接: http://yoursite.com/2019/02/22/Scrapy爬取p站热门插图/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 F1rry's blog