前言
作为Python入门者而言,最简单有趣且容易上手的锻炼项目便不得不提到爬虫了,估计学习python的同学,10个有8个第一个做的有趣的项目都是从爬虫开始的。对于一些简单的爬虫,不用10行代码便足以轻松应付,但对于一些比较“隐性”的网站,如动态网站,有时并不会将所有信息直白的显示在网页源代码中,这时候去做爬取动作,就犹如盲人摸象一般。
而此次介绍的这个例子便是如此。众所周知,Instagram,简称Ins或IG,是一个图片社交平台,对于想要爬取一些好看的图片或者偶像的图片的人来说,是一个很不错的平台。但美中的不足的是Ins对于爬虫入门来说挺不友好的,不像一般的图片门户网站那么轻易搞定。
思考逻辑
目标网页(例子):
搞笑語錄 (@funnyquotes_my) · Instagram 照片和视频
用浏览器打开网页(可能需要登陆才能显示目标页面),然后”F12检查元素>Network“,筛选“XHR”,观察网页刷新和内容加载过程。
图1
图2
按F5刷新页面,然后向下拉动页面,等瀑布流页面加载(异步加载)完成,会看到如图1所示,出现红框的html数据,事实上,这个数据是不完整的,里面只有12张图片的链接(可以点击右侧的preview,然后展开内部的json数据,如图2),所以你会看到在该红框的下方又有一个非常类似的数据(?query=hash=02e。。。),里面也一样只含有12张图片链接。
因为页面是异步加载,只有当滚动条快拉到底之后,新的网页内容才会加载出来。(就是左侧的 ?query=hash=02e。。。)
问题是怎么知道到底还有没有更多新的数据会被加载呢,或者说怎么知道几时全部加载完成,因为有些账号几千张图片,不可能一直这样往下拉,也不现实。
解决方法是查看刚才preview内的page_info数据,如下图所示。 has next page = True,则代表依旧存在下一页。而end cursor对应的一长串数值,便是下一页url的相关参数。
图3
为什么说这个end cursor的值便是下一页的所需的参数呢?
因为通过比对相近的两个(?query=hash=02e。。。)内的请求网址:
前一个(且称之为url-1):
https://www.instagram.com/graphql/query/?
query_hash=02e14f6a7812a876f7d133c9555b1151&variables=%7B%22id%22%3A%229419909664
%22%2C%22first%22%3A12%2C%22after%22%3A%22QVFDRXRkV0UxQmhseE5zMkVBTkdqSDQ
za1V5ZHN2Y1VjaTVxYkR2NENTdUxuQXhFLVVkdUt0dnpHeDBqdHRpTnIweVRyNExHTFdkWmti
YURIbW5GVkdZbw%3D%3D%22%7D
对应的后一个(且称之为url-2):
https://www.instagram.com/graphql/query/?
query_hash=02e14f6a7812a876f7d133c9555b1151&variables=%7B%22id%22%3A%229419909664%
22%2C%22first%22%3A12%2C%22after%22%3A%22QVFDSkVna3UyZjh4MjRiOHB6OEZWek5GZER
mejhJMVFBSHhuTTFJY2x6b0VZQnpWc2dVTDZCT05wUGlKLWQ3SWZsN1JrSVB4RXQ4eWNIdlRZ
Y09pUXlYXw%3D%3D%22%7D
通过比较,不难发现,他们仅仅只有加粗字体的这部分数值不同,而url-2内的加粗数值,就是刚才我们看到的url-1里preview内json数据的end cursor的数值。
所以说,观察发现:
这几个Json数据的url,除了加粗字符串不同,其余内容均相同
前面的Json数据的‘has next page’键值为‘True’,表明还有未加载页面;只有最后一个数据的‘has next page’键值为‘False’,表明页面已完全加载。
前面的Json数据,均有字符串‘end cursor=“xxxx”’,恰好是下一个Json数据的url中的加粗字符串,因此可以指向下一个Json数据的url。
解题思路
所以,理论上,只要我们获取了第一个最初始的网页url(在我的代码内,我称之为first url),我们就可以爬取到对应第二页参数的end cursor参数,然后一直循环获取,直到has more page = False,便不再获取。
这样,我们就获得了真正意义上的整个网页源码信息,才能将其中的图片链接全部获取下来。(*实际上也不是全部,留待最后解释)
至此,我们的思路就完全确定了:
- get网页url,获取第一批源码。
- 用上述方法判断页面是否有未加载页面;如存在,从第一批源码中找到第一个Json数据url的“加粗字符串”,从而获得其url。
- 从第一个Json数据的内容判断是否存在第二个Json数据,并获得其url。
- 不断获取Json数据的url,直至页面完全加载。
- 从这些url中获取完整源码,并爬取图片。
以下,就展示一下我的见解,全部代码如下:
Github:ChanForWang/python-web-crawler--Instagram-github.com
import requests
import os
#request 网址,并返回json data
def getHTML(url):
headers = {
#这边请输入你自己的浏览器的对应参数,不要照抄,否则不work
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/。。。。。,
'cookie': 'mid=YKc58AALAAFoSfrpLcjRB1RpuqGU; 。。。。。。'
}
html = requests.get(url,headers=headers)
data = html.json()
return data
#获取所有网址list,一个网址里包含12个url图片src
def getURLlist(url,id):
url_list = [url]
while url != None or has_next_page_data_info['has_next_page'] == True:
data = getHTML(url)
#判断有没有下一个page
has_next_page_data_info = data['data']['user']['edge_owner_to_timeline_media']['page_info']
if has_next_page_data_info['has_next_page'] == True:
#获取下一页所需要的新的after参数
after_new = has_next_page_data_info['end_cursor'].rstrip('=')
url = "https://www.instagram.com/graphql/query/?query_hash=02e14f6a7812a876f7d133c9555b1151&variables=%7B%22id%22%3A%22"+id+"%22%2C%22first%22%3A12%2C%22after%22%3A%22" + after_new + "%3D%3D%22%7D"
url_list.append(url)
else:
break
return url_list
#获取图片list
def getImageURL(data):
img_data_info = data['data']['user']['edge_owner_to_timeline_media']['edges']
#print(img_data_info)
img_url_list = []
for i in img_data_info:
img_url = i['node']['display_url']
img_url_list.append(img_url)
#print(img_url_list)
print("共"+str(len(img_url_list))+"张")
return img_url_list
#访问图片url list内的url,然后下载图片
def scapyImage(img_url_list,id):
for img_url in img_url_list:
img_data = requests.get(img_url).content
img_name = img_url.split('?')[0].split('/')[-1].split('_')[0] + ".jpg"
file_name = "id=" + id
#判断文件夹是否存在,不存在便新建一个
if not os.path.exists(file_name):
os.mkdir(file_name)
with open(file_name+"\\" + img_name,"wb") as f:
print('正在下载:' + img_name)
f.write(img_data)
if __name__ == "__main__":
id = input('请输入该个人IG的id参数是(在network的字符参数查看):')
after = input('请输入after参数:')
print("正在准备中,请稍后。。。")
#目标网址,第一个url
first_url = 'https://www.instagram.com/graphql/query/?query_hash=02e14f6a7812a876f7d133c9555b1151&variables=%7B%22id%22%3A%22'+id+ '%22%2C%22first%22%3A12%2C%22after%22%3A%22'+after+'%3D%3D%22%7D'
url_list = getURLlist(first_url,id)
for url_li in url_list:
print('==============正在下载第{}页数据:'.format(str(url_list.index(url_li)+1)))
data = getHTML(url_li)
img_url_list = getImageURL(data)
scapyImage(img_url_list,id)
把id和after参数复制到到input的回答框中
最后
如果一切顺利,你将获得一个以id=xxx开头的文件夹,里面便是你爬取的图片啦,如下:
瑕疵
事实上,这个脚本并不完美,如果有仔细研究的小伙伴应该不难发现,该爬虫并不是完全爬取了所有图片,会少了最新发布的12张。
这是因为我们设置的first url 他不包好最新发布的12张图片,这个作为我们爬虫入口的第一个爬取的网页,实际上是真是环境的“第二页”,也就是说我们的这个爬虫他是从第二页开始爬的。
为什么会出现这样的问题呢?主要是因为“第一页”他不是异步加载的,是直接呈现的,所以在Network中的XHR我们是找不到的。如果大家想把“第一页”也爬下来,其实完全不难,这个问题就当作“课后作业”留给大家自己思考吧!
参考:
发表评论
您尚未登录,登录后方可评论~~登陆 or 注册