分析今日頭條的Ajax請求抓取網頁數據,此次抓取今日頭條的街拍美圖,抓取完成後將每組圖片分文件夾下載到本地並保存下來。
1 抓取前的分析工做
首先分析抓取邏輯,打開今日頭條首頁:http://www.toutiao.com/。右上角有一個搜索入口,因爲要抓取街拍美圖,因此輸入「街拍」二字進行搜索。出現搜索結果頁面。
接着打開開發者工具,查看全部的網絡請求。首先打開第一個網絡請求,這個請求的URL就是當前的連接 https://www.toutiao.com/search/?keyword=街拍,打開Preview選項卡查看Response Body。若是頁面中的內容是根據第一個請求獲得的結果渲染出來的,那末第一個請求的源代碼中必然會包含頁面結果中的文字。同時按下ctrl+F調出搜索框,輸入「43歲李小冉」,如圖4-1所示。
圖4-1 搜索結果
從圖上可知,網頁源代碼中沒有包含這幾個字,搜索匹配結果數目爲0。由此可大體判斷這些內容是由Ajax加載,用JavaScript渲染出來的。切換到XHR過濾選項卡,發現有Ajax請求,看看它的結果是否包含了頁面中的相關數據。
點擊data字段展開,發現這裏有許多條數據。點擊第6條展開,有一個title字段對應的值正好是頁面中第一條數據的標題。檢查其餘數據,也有對應的關聯。如圖4-2所示。
圖4-2 對比結果
這裏一組圖就對應前面data字段中的一條數據。每條數據還有一個image_list字段,以列表形式呈現,這其中就包含了組圖的全部圖片列表。如圖4-3所示。
圖4-3 圖片列表信息
所以,只須要將列表中的url字段提取出來並下載下來就行。每一組都創建一個文件夾,文件夾名稱就是組圖的標題。接下來就可直接用Python來模擬這個Ajax請求,而後提取出相關美圖鏈接並下載。在這以前還須要分析一下URL的規律。回到Headers選項卡,觀察下它的請求URL和Headers信息。例以下面這個URL所示:
Request URL: https://www.toutiao.com/api/search/content/?aid=24&app_name=web_search&offset=0&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&en_qc=1&cur_tab=1&from=search_tab&pd=synthesis
能夠看到這是一個GET請求,請求URL參數有aid、app_name、offset、format、keyword、autoload、count、en_qc、cur_tab、from和pd。須要找出這些參數的規律才能方便的用程序構造出來。接着往下滑動頁面,多加載一些新結果。在加載的同時能夠發現,NetWork中又出現了許多Ajax請求。觀察URL變化可知,變化的參數只有offset,其餘參數都沒有變化,並且第二次請求的offset值爲20,第三次爲40,第四次爲60,...,這個規律就是offset值,也就是偏移量,從而推斷出count參數就是一次性獲取的數據條數。所以可用offset參數來控制數據分頁。這樣就能夠經過接口批量獲取數據,將數據解析,再將圖片下載下來便可。
3 實際操做經過前面的Ajax請求邏輯分析,下面就用程序來實現美圖下載。第一步:實現g et_page() 方法加載單個Ajax請求的結果。有變化的參數是offset,把它看成參數傳遞,代碼以下:import requestsfrom urllib.parse import urlencodedef get_page(offset): """請求頁面的方法""" params = { 'aid': '24', 'app_name': 'web_search', 'offset': offset, 'format': 'json', 'keyword': '街拍', 'autoload': 'true', 'count': '20', 'en_qc': '1', 'cur_tab': '1', 'from':'search_tab', 'pd': 'synthesis', } url = 'https://www.toutiao.com/api/search/content/?' + urlencode(params) try: response = requests.get(url) if response.status_code == 200: return response.json() except requests.ConnectionError: return None這裏的代碼中用 urlencode() 方法構造請求的GET參數,而後用 requests 請求這個連接,若是返回狀態碼爲200,則調用 response 的 json() 方法將結果轉爲JSON格式後返回。第二步實現解析方法:提取每條數據的 image_detail 字段中的每一張圖片連接,將圖片連接和圖片所屬的標題一併返回,可構造生成器來實現。代碼以下:import redef get_images(json): """解析頁面的方法""" if json.get('data'): # data是一個字典列表,下面的for循環是在循環列表每一個元素 for item in json.get('data'): # 有的元素沒有title字段,不須要爬取 if not item.get('title'): continue else: title = item.get('title') images = item.get('image_list') large_image_url = item.get('large_image_url') for image in images: image_list_url = image.get('url') try: # 有部分url匹配不成功,獲取image_list中的url後半部分及large_image_url的前半部分 image_list_url_end = re.search('http.*/(.*)', image_list_url).group(1) large_image_url_start = re.search("(http:.*/).*", large_image_url).group(1) except AttributeError: print(image_list_url) continue image_url = large_image_url_start + image_list_url_end yield { 'image': image_url, 'title': title }第三步實現保存圖片的方法 save_image():該方法有一個 item 參數,item參數是由 get_images() 方法返回的一個字典。在這個方法中,首先根據item的title建立文件夾,而後請求這個連接,獲取圖片的二進制數據,以二進制形式寫入文件。圖片名稱可以使用其內容的MD5值以免重複。代碼以下所示:import osfrom hashlib import md5def save_image(item): """保存圖片:根據標題建立文件夾,根據圖片內容生成MD5值做圖片名稱來保存圖片""" if not os.path.exists(item.get('title')): os.mkdir(item.get('title')) try: response = requests.get(item.get('image')) if response.status_code == 200: file_path = '{0}/{1}.{2}'.format(item.get('title'), md5(response.content).hexdigest(), 'jpg') if not os.path.exists(file_path): with open(file_path, 'wb') as f: f.write(response.content) else: print('Already Downloaded', file_path) except requests.ConnectionError: print('Failed to Save Image')第四步:構造一個offset數組,遍歷offset,提取圖片連接,將將其下載便可:from multiprocessing.pool import Pooldef main(offset): json = get_page(offset) for item in get_images(json): print(item) save_image(item)GROUP_START = 0GROUP_END = 20if __name__ == '__main__': pool = Pool() groups = ([x * 20 for x in range(GROUP_START, GROUP_END)]) pool.map(main, groups) pool.close() pool.join()代碼中的 GROUP_START 和 GROUP_END 分別是起始頁數和終止頁數,還利用了多線程的線程池,調用其map方法實現多線程下載。運行程序成功後,在程序代碼所在的文件夾就可看到街拍圖片都分文件夾保存了下來。經過上面的實例操做知道了Ajax分析流程、Ajax分頁的模擬以及圖片的下載過程。