Python instagram 爬蟲項目


直接介紹一下具體的步驟以及注意點:

instagram 爬蟲注意點

  • instagram 的首頁數據是 服務端渲染的,所以首頁出現的 11 或 12 條數據是以 html 中的一個 json 結構存在的(additionalData), 之后的帖子加載才是走 ajax 請求的

  • 在 2019/06 之前,ins 是有反爬機制的,請求時需要在請求頭加了 'X-Instagram-GIS' 字段。其算法是:
    1、將 rhx_gis 和 queryVariables 進行組合

    rhx_gis 可以在首頁處的 sharedData 這個 json 結構中獲得
    注意:很多人學Python過程中會遇到各種煩惱問題解決不了。為此小編建了個Python全棧免費答疑交流.裙 :一久武其而而流一思(數字的諧音)轉換下可以找到了,不懂的問題有老司機解決里面還有最新Python教程項目可拿,,一起相互監督共同進步!

    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.instagram.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: a5164aed103f24b03e7b7747a2d94e3c variables: { "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 requests import re import json import urllib.parse import hashlib import sys USER_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.instagram.com/graphql/query/?query_hash=42323d64886122307be10013ad2dcc44&variables=%s" ACCOUNT_PAGE = 'https://www.instagram.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 account def 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 medias def 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_name def 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_token def 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 header def 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 res def 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 account account = get_account('shaq') result = get_media_by_user_id(account['id'], 56) media = get_media_by_url('https://www.instagram.com/p/Bw3-Q2XhDMf/') print(len(result)) print(result) 
 

封裝成庫了!

本次分享大家都看懂了嗎? 另外注意很多人學Python過程中會遇到各種煩惱問題解決不了。為此小編建了個Python全棧免費答疑交流.裙 :一久武其而而流一思(數字的諧音)轉換下可以找到了,不懂的問題有老司機解決里面還有最新Python教程項目可拿,,一起相互監督共同進步!
本文的文字及圖片來源於網絡加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理。
————————————————
版權聲明:本文為CSDN博主「編程叫獸」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/QQ2352108083/article/details/104406925


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM