最近上p站遇到喜欢的画师,想下载他所有的图片,感觉一个一个点太麻烦了,就想能不能用爬虫试一下爬取图片。
p站爬取的主要难点在于动态页面与图片懒加载。
看了一下p站源代码,发现是ajax异步传输,可是本渣又不会Selenium模拟浏览器之类的操作。抱着试一试的心态爬了某位画师主页惊喜地发现里面是有画师作品的ID的。于是就先随便写了一个爬了一下:
import requests
from bs4 import BeautifulSoup
import os
import time
header={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0',
'Referer':'https://www.pixiv.net/showcase/a/3757/'
}
os.system("start "+"你的VPN路径")
id=input("请输入作者ID:")
maxpage=eval(input("最大下载页数:"))
url='https://www.pixiv.net/member_illust.php?id='+id
for i in range(1,maxpage+1):
src=url+"&p="+str(i)
picpage=requests.get(src,headers=header)
soup=BeautifulSoup(picpage.text,"html.parser")
pic_s=soup.find_all('img')
for m in range(1,49):
try:
pic_=pic_s[m]
picname=pic_['alt']
appendurl="/".join(pic_['src'].split('/')[-7:])
html="https://i.pximg.net/img-original/img/"+appendurl
html1=list(html)
for i in range(11):
del html1[-5]
html="".join(html1)
print(picname+" 正在下载...")
if "404 Not Found" in str(html2.text):
html1=list(html)
html1[-1]="g"
html1[-2]="n"
html1[-3]="p"
a=".png"
html="".join(html1)
html2=requests.get(html,headers=header)
time.sleep(1)
else:
a=".jpg"
html2=requests.get(html,headers=header)
with open("你的文件保存路径"+picname+a,'wb') as file:
file.write(html2.content)
print(picname+" 下载完成,开始下载下一张图片")
time.sleep(5)
except:
print(picname+" 下载失败,开始下载下一张图片")
os.system("taskkill -f -im ShadowsocksR-dotnet4.0.exe")
(PS:1、爬的时候要注明UA,UA可以用网上的,也可以在p站页面用浏览器审查元素功能找到:
2、现在p站必须用科学上网才能上,所以在程序的最开始要用os的system函数把VPN软件打开,软件资源就靠自己了。
3、代码中28行有一个if判断是为了检验p站图片格式,一般来说有png与jpg两种。如果后缀名不对的话是不能下载图片的(只能下到一段404 Not Found的代码)
用了以后确实可以下载到图片(有概率被p站限制访问)。但是仔细看了一下发现最多只能下20张左右的图片。检查了一下,发现所谓的作品ID的列表根本就不全。(可能是requests爬地太快了)于是乎,我盯着源代码看了一个小时,虽然可以找到作品列表的ID,但是靠requests貌似是爬不下来的。当我快要放弃的时候,我发现单个作品的网页里面可以看见前后几部同画师作品的ID。这样思路就来了:
1、先找到第一个图片的ID,这样进入这个图片的页面。
2、读取这个页面所有能看见的图片的ID 并下载,把最后一个图片的ID加上网页前缀,进入这个图片的网页
3、重复2的过程,就可以下载任意张数的图片且不会有遗漏了。
因为有了第一个的经验,这次的代码还算比较好写
这里直接贴上来:
#请用管理员权限打开,否则无法终止VPN进程
import requests
from bs4 import BeautifulSoup
import os
import time
#启动VPN,设置请求头,路径,画师,下载张数
os.system("start G:\sR\ShadowsocksR-dotnet4.0.exe")
header={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0',
'Referer':'https://www.pixiv.net/'
}
chd=input("请输入存储路径:")
id=input("请输入作者ID:")
max_=eval(input("最大下载张数:"))
os.chdir(chd)
#初始化第一张图片的链接
url='https://www.pixiv.net/member.php?id='+id
picpage=requests.get(url,headers=header)
soup=BeautifulSoup(picpage.text,"html.parser")
picblank=soup.find_all('img')
picfirst=picblank[1]
picnamefirst=picfirst['alt']
firstid=picfirst['src'].split('/')[-1].split("_")[0]
url1=requests.get("https://www.pixiv.net/member_illust.php?mode=medium&illust_id="+firstid)
count=0
l=7
#循环下载
for i in range(50):
#寻找图片所在标签
souppic=BeautifulSoup(url1.text,"html.parser")
picblank=souppic.find_all('img')
try:
#寻找预加载图片规律
picture=picblank[1:l]
if picture[5]['alt'].split("/")[0]==picture[0]['alt'].split("/")[0]:
l+=1
picture=picblank[1:l]
except:
break
for k in range(l-1):
try:
#获取图片名
picname=picture[k]['alt'].split("/")[0]
#判断图片是否已存在,如是则跳过
if os.path.isfile(picname+".jpg")==True or os.path.isfile(picname+".png")==True:
print(picname+" 已存在")
continue
else:
count+=1
#构造下载链接,if判断检验链接的后缀是png还是jpg
appendurl="/".join(picture[k]['src'].split('/')[-7:])
html="https://i.pximg.net/img-original/img/"+appendurl
html1=list(html)
for i in range(11):
del html1[-5]
html="".join(html1)
print(picname+" 正在下载...")
html2=requests.get(html,headers=header)
if "404 Not Found" in str(html2.text):
html1=list(html)
html1[-1]="g"
html1[-2]="n"
html1[-3]="p"
a=".png"
html="".join(html1)
html2=requests.get(html,headers=header)
time.sleep(1)
else:
a=".jpg"
#将数据写入图片
with open(picname+a,'wb') as file:
file.write(html2.content)
#检验是否达到下载数量,如是跳出循环
if count==max_:
print("所有图片下载完成")
break
print(picname+" 下载完成,开始下载下一张图片")
#延时避免反爬
time.sleep(5)
except:
print(picname+" 下载失败,开始下载下一张图片")
if count==max_:
break
#以源代码的最后一张图片为初始,构造下一个下载循环
piclastid=picture[l-2]['src'].split('/')[-1].split("_")[0]
url1=requests.get("https://www.pixiv.net/member_illust.php?mode=medium&illust_id="+piclastid)
#程序结束,关闭VPN
os.system("taskkill -f -im ShadowsocksR-dotnet4.0.exe")
ID与网址的获取直接观察标签结构就可以了。加了count是为了控制下载图片的数量。
l=7
picture=picblank[1:l]
if picture[5]['alt'].split("/")[0]==picture[0]['alt'].split("/")[0]:
l+=1
picture=picblank[1:l]
这个是因为不同的作品页面能够显示的作品数量不一样。l过小是可能造成死循环的。至于这个判断有点难解释,就是源代码中所有'img'这个标签的排列是这样的:“画师头像+前后作品列表(中间也有爬取的页面的作品)”。所以我直接检验所选的前后作品列表的最后一项是不是爬取的页面的作品,如果是就死循环了,把l加1,就能读取到后面的作品,循环就会继续前进。(其实这里应该写一个for循环,因为l可能不止7,8两种情况)
至于效果,可以说基本能爬(多图,动图还有一些原因不明的bug图片爬不了),有一个缺点是容易被p站限制访问。不知道有没有好的解决方法。
代码有些乱,以后有时间再写一遍吧。
(PS:网上爬取动态页面的方法对p站可能并不适用。因为p站的作品是按ID保存,同一个画师的作品ID是没有规律的)