前言

作为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,便不再获取。

 

这样,我们就获得了真正意义上的整个网页源码信息,才能将其中的图片链接全部获取下来。(*实际上也不是全部,留待最后解释)

 

至此,我们的思路就完全确定了:

  1. get网页url,获取第一批源码。
  2. 用上述方法判断页面是否有未加载页面;如存在,从第一批源码中找到第一个Json数据url的“加粗字符串”,从而获得其url。
  3. 从第一个Json数据的内容判断是否存在第二个Json数据,并获得其url。
  4. 不断获取Json数据的url,直至页面完全加载。
  5. 从这些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我们是找不到的。如果大家想把“第一页”也爬下来,其实完全不难,这个问题就当作“课后作业”留给大家自己思考吧!

 

参考:

python爬取蚂蜂窝游记图片:从XHR入手爬取异步加载(动态加载)网页

延伸阅读
  1. 上一篇: 多进程 vs 多线程 + python 多线程爬虫(图片)效率提升
  2. 下一篇: 网络通信协议简介(TCP/IP Networking)

发表评论

您尚未登录,登录后方可评论~~
登陆 or 注册

评论列表

暂无评论哦~~