首页新闻动态正文

python instagram 爬虫【黑马python培训】

更新时间:2019年07月26日 10时50分33秒 来源:黑马程序员论坛

直接介绍一下具体的步骤以及注意点:

instagram 爬虫注意点
  • instagram 的首页数据是 服务端渲染的,所以首页出现的 11 或 12 条数据是以 html 中的一个 json 结构存在的(additionalData), 之后的帖子加载才是走 ajax 请求的

  • 在 2019/06 之前,ins 是有反爬机制的,请求时需要在请求头加了 'X-Instagram-GIS' 字段。其算法是:
    1、将 rhx_gis 和 queryVariables 进行组合

    rhx_gis 可以在首页处的 sharedData 这个 json 结构中获得

    2、然后进行 md5 哈希
    e.g.

        queryVariables = '{"id":"' + user_id + '","first":12,"after":"' +cursor+ '"}'    print(queryVariables)    headers['X-Instagram-GIS' = hashStr(GIS_rhx_gis + ":" + queryVariables)
  • 但是在在 2019/06 之后, instagram 已经取消了 X-Instagram-GIS 的校验,所以无需再生成 X-Instagram-GIS,上一点内容可以当做历史来了解了

  • 初始访问 ins 首页的时候会设置一些 cookie,设置的内容 (response header) 如下:

        set-cookie: rur=PRN; Domain=.instagram.com; HttpOnly; Path=/; Secure    set-cookie: ds_user_id=11859524403; Domain=.instagram.com; expires=Mon, 15-Jul-2019 09:22:48 GMT; Max-Age=7776000; Path=/; Secure    set-cookie: urlgen="{\"45.63.123.251\": 20473}:1hGKIi:7bh3mEau4gMVhrzWRTvtjs9hJ2Q"; Domain=.instagram.com; HttpOnly; Path=/; Secure    set-cookie: csrftoken=Or4nQ1T3xidf6CYyTE7vueF46B73JmAd; Domain=.instagram.com; expires=Tue, 14-Apr-2020 09:22:48 GMT; Max-Age=31449600; Path=/; Secure
  • 关于 query_hash,一般这个哈希值不用怎么管,可以直接写死

  • 特别注意:在每次请求时务必带上自定义的 header,且 header 里面要有 user-agent,这样子才能使用 rhx_gis 来进行签名访问并且获取到数据。切记!是每次访问!例如:

    headers = {    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'}
  • 大部分 api 的访问需要在请求头的 cookie 中携带 session-id 才能得到数据,一个正常的请求头 (request header) 如下:

        :authority: www.aiidol.com    :method: GET    :path: /graphql/query/?query_hash=ae21d996d1918b725a934c0ed7f59a74&variables=%7B%22fetch_media_count%22%3A0%2C%22fetch_suggested_count%22%3A30%2C%22ignore_cache%22%3Atrue%2C%22filter_followed_friends%22%3Atrue%2C%22seen_ids%22%3A%5B%5D%2C%22include_reel%22%3Atrue%7D    :scheme: https    accept: */*    accept-encoding: gzip, deflate, br    accept-language: zh-CN,zh;q=0.9,en;q=0.8,la;q=0.7    cache-control: no-cache    cookie: mid=XI-joQAEAAHpP4H2WkiI0kcY3sxg; csrftoken=Or4nQ1T3xidf6CYyTE7vueF46B73JmAd; ds_user_id=11859524403; sessionid=11859524403%3Al965tcIRCjXmVp%3A25; rur=PRN; urlgen="{\"45.63.123.251\": 20473}:1hGKIj:JvyKtYz_nHgBsLZnKrbSq0FEfeg"    pragma: no-cache    referer: https://www.instagram.com/    user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36    x-ig-app-id: 936619743392459    x-instagram-gis: 8f382d24b07524ad90b4f5ed5d6fccdb    x-requested-with: XMLHttpRequest

    注意 user-agent、x-ig-app-id (html 中的 sharedData 中获取)、x-instagram-gis,以及 cookie 中的 session-id 配置

  • api 的分页 (请求下一页数据),如用户帖子列表
    ins 中一个带分页的 ajax 请求,一般请求参数会类似下面:

    query_hash: a5164aed103f24b03e7b7747a2d94e3cvariables: {"id":"1664922478","first":12,"after":"AQBJ8AGqCb5c9rO-dl2Z8ojZW12jrFbYZHxJKC1hP-nJKLtedNJ6VHzKAZtAd0oeUfgJqw8DmusHbQTa5DcoqQ5E3urx0BH9NkqZFePTP1Ie7A"}

    -- id 表示用户 id,可在 html 中的 sharedData 中获取
    -- first 表示初始时获取多少条记录,好像最多是 50
    -- after 表示分页游标,记录了分页获取的位置

    当然 variables 部分里面的参数根据请求的 api 不同而可能不同 (不止这么少),这里只列出与分页相关的参数。

    分页请求参数首先是从 html 中的 sharedData 中获取的:

        # 网页页面信息    page_info = js_data["entry_data"["ProfilePage"[0["graphql"["user"["edge_owner_to_timeline_media"['page_info'    # 下一页的索引值AQCSnXw1JsoV6LPOD2Of6qQUY7HWyXRc_CBSMWB6WvKlseC-7ibKho3Em0PEG7_EP8vwoXw5zwzsAv_mNMR8yX2uGFZ5j6YXdyoFfdbHc6942w    cursor = page_info['end_cursor'    # 是否有下一页    flag = page_info['has_next_page'

    end_cursor 即为 after 的值,has_next_page 检测是否有下一页
    如果是有下一页,可进行第一次分页数据请求,第一次分页请求的响应数据回来之后,id,first 的值不用变,after 的值变为响应数据中 page_info 中 end_cursor 的值,再构造 variables,连同 query_hash 发起再下一页的请求
    再判断响应数据中的 page_info 中 has_next_page 的值,循环下去,可拿完全部数据。若不想拿完,可利用响应数据中的 edge_owner_to_timeline_media 中的 count 值来做判断,该值表示用户总共有多少媒体

  • 视频帖子和图片帖子数据结构不一样,注意判断响应数据中的 is_video 字段

  • 如果是用一个 ins 账号去采集的话,只要请求头的 cookie 中带上合法且未过期的 session_id,可直接访问接口,无需计算签名。
    最直接的做法是:打开浏览器,登录 instagram 后,F12 查看 xhr 请求,将 request header 中的 cookie 复制过来使用即可,向下面:

    headers = {    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',    'cookie': 'mid=XLaW9QAEAAH0WaPDCeY490qeeNlA; csrftoken=IgcP8rj0Ish5e9uHNXhVEsTId22tw8VE; ds_user_id=11859524403; sessionid=11859524403%3A74mdddCfCqXS7I%3A15; rur=PRN; urlgen="{\"45.63.123.251\": 20473}:1hGxr6:Phc4hR68jNts4Ig9FbrZRglG4YA"'}

    在请求发出的时候带上类似上面的请求头

  • 错误日志记录表在 192.168.1.57 中 zk_flock 库的 ins_error_log,目前比较多 unknow ssl protocol 类型的错误,怀疑是爬取太快的原因,需要一个代理来切换


给出能运行的代码?(设置了 FQ 代理,不需要的可以去掉喔):

# -*- coding:utf-8 -*-import requestsimport reimport jsonimport urllib.parseimport hashlibimport sysUSER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'BASE_URL = 'https://www.instagram.com'ACCOUNT_MEDIAS = "http://www.smpeizi.com/graphql/query/?query_hash=42323d64886122307be10013ad2dcc44&variables=%s"ACCOUNT_PAGE = 'https://www.pzzs168.com/%s'proxies = {    'http': 'http://127.0.0.1:1087',    'https': 'http://127.0.0.1:1087',}# 一次设置proxy的办法,将它设置在一次session会话中,这样就不用每次都在调用requests的时候指定proxies参数了# s = requests.session()# s.proxies = {'http': '121.193.143.249:80'}def get_shared_data(html=''):    """get window._sharedData from page,return the dict loaded by window._sharedData str    """    if html:        target_text = html    else:        header = generate_header()        response = requests.get(BASE_URL, proxies=proxies, headers=header)        target_text = response.text    regx = r"\s*.*\s*<script.*?>.*_sharedData\s*=\s*(.*?);<\/script>"    match_result = re.match(regx, target_text, re.S)    data = json.loads(match_result.group(1))    return data# def get_rhx_gis():#     """get the rhx_gis value from sharedData#     """#     share_data = get_shared_data()#     return share_data['rhx_gis']def get_account(user_name):    """get the account info by username    :param user_name:    :return:    """    url = get_account_link(user_name)    header = generate_header()    response = requests.get(url, headers=header, proxies=proxies)    data = get_shared_data(response.text)    account = resolve_account_data(data)    return accountdef get_media_by_user_id(user_id, count=50, max_id=''):    """get media info by user id    :param id:    :param count:    :param max_id:    :return:    """    index = 0    medias = [    has_next_page = True    while index <= count and has_next_page:        varibles = json.dumps({            'id': str(user_id),            'first': count,            'after': str(max_id)        }, separators=(',', ':'))  # 不指定separators的话key:value的:后会默认有空格,因为其默认separators为(', ', ': ')        url = get_account_media_link(varibles)        header = generate_header()        response = requests.get(url, headers=header, proxies=proxies)        media_json_data = json.loads(response.text)        media_raw_data = media_json_data['data'['user'['edge_owner_to_timeline_media'['edges'        if not media_raw_data:            return medias        for item in media_raw_data:            if index == count:                return medias            index += 1            medias.append(general_resolve_media(item['node'))        max_id = media_json_data['data'['user'['edge_owner_to_timeline_media'['page_info'['end_cursor'        has_next_page = media_json_data['data'['user'['edge_owner_to_timeline_media'['page_info'['has_next_page'    return mediasdef get_media_by_url(media_url):    response = requests.get(get_media_url(media_url), proxies=proxies, headers=generate_header())    media_json = json.loads(response.text)    return general_resolve_media(media_json['graphql'['shortcode_media')def get_account_media_link(varibles):    return ACCOUNT_MEDIAS % urllib.parse.quote(varibles)def get_account_link(user_name):    return ACCOUNT_PAGE % user_namedef get_media_url(media_url):    return media_url.rstrip('/') + '/?__a=1'# def generate_instagram_gis(varibles):#     rhx_gis = get_rhx_gis()#     gis_token = rhx_gis + ':' + varibles#     x_instagram_token = hashlib.md5(gis_token.encode('utf-8')).hexdigest()#     return x_instagram_tokendef generate_header(gis_token=''):    # todo: if have session, add the session key:value to header    header = {        'user-agent': USER_AGENT,    }    if gis_token:        header['x-instagram-gis' = gis_token    return headerdef general_resolve_media(media):    res = {        'id': media['id',        'type': media['__typename'[5:.lower(),        'content': media['edge_media_to_caption'['edges'[0['node'['text',        'title': 'title' in media and media['title' or '',        'shortcode': media['shortcode',        'preview_url': BASE_URL + '/p/' + media['shortcode',        'comments_count': media['edge_media_to_comment'['count',        'likes_count': media['edge_media_preview_like'['count',        'dimensions': 'dimensions' in media and media['dimensions' or {},        'display_url': media['display_url',        'owner_id': media['owner'['id',        'thumbnail_src': 'thumbnail_src' in media and media['thumbnail_src' or '',        'is_video': media['is_video',        'video_url': 'video_url' in media and media['video_url' or ''    }    return resdef resolve_account_data(account_data):    account = {        'country': account_data['country_code',        'language': account_data['language_code',        'biography': account_data['entry_data'['ProfilePage'[0['graphql'['user'['biography',        'followers_count': account_data['entry_data'['ProfilePage'[0['graphql'['user'['edge_followed_by'['count',        'follow_count': account_data['entry_data'['ProfilePage'[0['graphql'['user'['edge_follow'['count',        'full_name': account_data['entry_data'['ProfilePage'[0['graphql'['user'['full_name',        'id': account_data['entry_data'['ProfilePage'[0['graphql'['user'['id',        'is_private': account_data['entry_data'['ProfilePage'[0['graphql'['user'['is_private',        'is_verified': account_data['entry_data'['ProfilePage'[0['graphql'['user'['is_verified',        'profile_pic_url': account_data['entry_data'['ProfilePage'[0['graphql'['user'['profile_pic_url_hd',        'username': account_data['entry_data'['ProfilePage'[0['graphql'['user'['username',    }    return accountaccount = get_account('shaq')result = get_media_by_user_id(account['id', 56)media = get_media_by_url('https://www.idiancai.com/p/Bw3-Q2XhDMf/')print(len(result))print(result)
封装成库了!

除此以外,为了方便我写了一个库放在了 github 上,里面包含了很多操作,希望大家能看一下给点建议。如果对你有用的话,欢迎 star 和 PR~ 感谢泥萌!!


推荐了解热门学科

java培训 Python人工智能 Web前端培训 PHP培训
区块链培训 影视制作培训 C++培训 产品经理培训
UI设计培训 新媒体培训 产品经理培训 Linux运维
大数据培训 智能机器人软件开发




传智播客是一家致力于培养高素质软件开发人才的科技公司“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。

传智播客从未停止思考

传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”

中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。

何为中高级程序员课程?

传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。




黑马程序员热门视频教程【点击播放】

Python入门教程完整版(懂中文就能学会) 零起点打开Java世界的大门
C++| 匠心之作 从0到1入门学编程 PHP|零基础入门开发者编程核心技术
Web前端入门教程_Web前端html+css+JavaScript 软件测试入门到精通


在线咨询 我要报名
和我们在线交谈!