python爬蟲從小白到高手 Day2 動態(tài)頁面的爬取

今天我們說說動態(tài)頁面的抓取最蕾,動態(tài)頁面的概念不是說網(wǎng)頁上的內(nèi)容是活動的,而是刷新的內(nèi)容由Ajax加載胚泌,頁面的URL沒有變化,具體概念問度娘肃弟。
就以男人都喜歡的美女街拍為例玷室,對象為今日頭條。
chrome打開今日頭條 ->搜索
https://www.toutiao.com/search/?keyword=街拍
開發(fā)者工具->network選項卡
圖2-1

2-1.png

很多條目笤受,各種請求穷缤,但Ajax其實有其特殊的請求類型,它叫作xhr箩兽。在圖6-3中津肛,我們可以發(fā)現(xiàn)一個名稱以getIndex開頭的請求,其Type為xhr汗贫,這就是一個Ajax請求身坐。用鼠標(biāo)點擊這個請求,可以查看這個請求的詳細(xì)信息落包。

圖2-2
2-2.png

選中這個xhr請求后部蛇,我們可以看到Request Headers中X-Requested-With:XMLHttpRequest,這就標(biāo)記了此請求是Ajax請求妥色。
點擊一下Preview乔妈,即可看到響應(yīng)的內(nèi)容绪氛,它是JSON格式的。這里Chrome為我們自動做了解析,點擊箭頭即可展開和收起相應(yīng)內(nèi)容玛臂,初步分析這里返回的是頁面上顯示出來的前二十條信息贴膘。

圖2-3
2-3.png

切換回第一個請求偶器,我們發(fā)現(xiàn)Response中的信息是這樣的
圖2-4


2-4.png

這就是原始鏈接 https://www.toutiao.com/search/?keyword=街拍 所返回的內(nèi)容捏题,只有六十多行代碼,執(zhí)行了一些JavaScript许师,所以我們最終看到的頁面不是由初始頁面返回的房蝉,而是后來執(zhí)行的JavaScript向服務(wù)器發(fā)送了Ajax請求僚匆,收到返回的真實數(shù)據(jù)后才顯示出來的。這就是動態(tài)頁面渲染的流程搭幻。
明白了整個流程后咧擂,我們要做的最重要的事就是分析返回數(shù)據(jù)的內(nèi)容,用python模擬Ajax請求檀蹋,拿到我們所希望抓取的數(shù)據(jù)松申。

def get_page(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
    }
    url = 'https://www.toutiao.com/search_content/?'
    try:
        response = requests.get(url, params=params)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError:
        return None

下滑幾次后,發(fā)現(xiàn)只有offset參數(shù)變化俯逾,所以贸桶,構(gòu)造url,requests獲得數(shù)據(jù)
這里拿到的數(shù)據(jù)是json格式的

def download_image(jsonData):
    if jsonData.get('data'):
        for item in jsonData.get('data'):
            if item and 'article_url' in item.keys():
                title = item.get('title')
                article_url = item.get('article_url')
                result = get_real_image_path(article_url)
                if result: save_to_mongo(result)
'''

            另外一種數(shù)據(jù)格式cell桌肴,cell type太多皇筛,主要分析上面一種
            else:
                #original_page_url
                data = item.get('display')
                #print(display)
                #data = json.loads(display)
                #print(data)
                if data and 'results' in data.keys():
                    results = data.get('results')
                    original_page_urls = [item.get('original_page_url') for item in results]
                # .get('results').get('original_page_url')
                #title = item.get('display').get('title')
                #print(title)
                #print(original_page_urls)'''
'''

取出數(shù)據(jù)中的data段,發(fā)現(xiàn)只有前四張圖片的地址可以取到坠七,剩下的圖片必須進(jìn)入文章頁才能獲得水醋,我們?nèi)〕鑫恼马摰膗rl,requests獲得文章頁數(shù)據(jù)

def get_real_image_path(article_url):
    headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    response = requests.get(article_url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")
    title = soup.select('title')[0].get_text()
    image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
    result = re.search(image_pattern, response.text)

    if result:
        result = result.group(1).replace('\\', '')
        data = json.loads(result)

        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images_urls = [item.get('url') for item in sub_images]
            for image_url in images_urls: download_real_image(image_url)

            return {
                'title': title,
                'url' : article_url,
                'image_urls': images_urls
            }

這里需要加入UA頭彪置,否則返回不了數(shù)據(jù)离例,拿到數(shù)據(jù)后,發(fā)現(xiàn)圖片地址位于

圖2-5
2-5.png

這里用正則表達(dá)式
gallery: JSON.parse("(.*?)"),
匹配符合條件的悉稠,gallery: JSON.parse("")中的數(shù)據(jù)()這里在正則中表達(dá)的是轉(zhuǎn)義字符,有興趣的可以學(xué)習(xí)一下正則表達(dá)式艘包,這里就不贅述了

我們從sub_images中拿到了所有圖片地址的猛,下載過程就很簡單了
requests圖片地址,獲得的response中的content就是圖片的數(shù)據(jù)

def download_real_image(url):
    print('downloading---', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except RequestException:
        print('request image fail---', url)
        return None

def save_image(content):
    files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
    if not os.path.exists(files_path):
        os.mkdir(files_path)

    file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)

我們還可以把圖片的標(biāo)題和地址寫入數(shù)據(jù)庫

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('save success', result)
        return True
    return False

完整代碼:jrtt.py

import requests
import re
import json
from hashlib import md5
import os
from bs4 import BeautifulSoup
import pymongo
from config import *
import time



client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]

def get_page(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
    }
    url = 'https://www.toutiao.com/search_content/?'
    try:
        response = requests.get(url, params=params)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError:
        return None

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('save success', result)
        return True
    return False

def download_real_image(url):
    print('downloading---', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except RequestException:
        print('request image fail---', url)
        return None


def save_image(content):
    files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
    if not os.path.exists(files_path):
        os.mkdir(files_path)

    file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)


def get_real_image_path(article_url):
    headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    response = requests.get(article_url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")
    title = soup.select('title')[0].get_text()
    image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
    result = re.search(image_pattern, response.text)


    if result:
        result = result.group(1).replace('\\', '')
        data = json.loads(result)

        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images_urls = [item.get('url') for item in sub_images]
            for image_url in images_urls: download_real_image(image_url)

            return {
                'title': title,
                'url' : article_url,
                'image_urls': images_urls
            }



def download_image(jsonData):
    if jsonData.get('data'):
        for item in jsonData.get('data'):
            if item and 'article_url' in item.keys():
                title = item.get('title')
                article_url = item.get('article_url')
                result = get_real_image_path(article_url)

                if result: save_to_mongo(result)
            '''
            另外一種數(shù)據(jù)格式cell想虎,cell type太多卦尊,主要分析上面一種
            else:
                #original_page_url
                data = item.get('display')
                #print(display)
                #data = json.loads(display)
                #print(data)
                if data and 'results' in data.keys():
                    results = data.get('results')
                    original_page_urls = [item.get('original_page_url') for item in results]
                # .get('results').get('original_page_url')
                #title = item.get('display').get('title')
                #print(title)
                #print(original_page_urls)'''


def main():

    STARTPAGE = 1
    ENDPAGE = 2

    for i in range(STARTPAGE, ENDPAGE):
        time.sleep(1)
        offset = i * 20
        jsonData = get_page(offset)
        download_image(jsonData)

if __name__ == "__main__":
    main()

config.py

MONGO_URL = 'localhost'

MONGO_DB = 'jiepai'

MONGO_TABLE = 'jiepai'

GROUP_START = 0

GROUP_END = 20

KEYWORD = '街拍'



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舌厨,隨后出現(xiàn)的幾起案子岂却,更是在濱河造成了極大的恐慌,老刑警劉巖裙椭,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躏哩,死亡現(xiàn)場離奇詭異,居然都是意外死亡揉燃,警方通過查閱死者的電腦和手機(jī)扫尺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炊汤,“玉大人正驻,你說我怎么就攤上這事弊攘。” “怎么了姑曙?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵襟交,是天一觀的道長。 經(jīng)常有香客問我伤靠,道長捣域,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任醋界,我火速辦了婚禮竟宋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘形纺。我一直安慰自己丘侠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布逐样。 她就那樣靜靜地躺著蜗字,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脂新。 梳的紋絲不亂的頭發(fā)上挪捕,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音争便,去河邊找鬼级零。 笑死,一個胖子當(dāng)著我的面吹牛滞乙,可吹牛的內(nèi)容都是我干的奏纪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼斩启,長吁一口氣:“原來是場噩夢啊……” “哼序调!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兔簇,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤发绢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后垄琐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體边酒,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年此虑,在試婚紗的時候發(fā)現(xiàn)自己被綠了甚纲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡朦前,死狀恐怖介杆,靈堂內(nèi)的尸體忽然破棺而出鹃操,到底是詐尸還是另有隱情,我是刑警寧澤春哨,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布荆隘,位于F島的核電站,受9級特大地震影響赴背,放射性物質(zhì)發(fā)生泄漏椰拒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一凰荚、第九天 我趴在偏房一處隱蔽的房頂上張望燃观。 院中可真熱鬧,春花似錦便瑟、人聲如沸缆毁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脊框。三九已至,卻和暖如春践啄,著一層夾襖步出監(jiān)牢的瞬間浇雹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工屿讽, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留昭灵,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓伐谈,卻偏偏與公主長得像虎锚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衩婚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容

  • AJAX 原生js操作ajax 1.創(chuàng)建XMLHttpRequest對象 var xhr = new XMLHtt...
    碧玉含香閱讀 3,184評論 0 7
  • 本文詳細(xì)介紹了 XMLHttpRequest 相關(guān)知識,涉及內(nèi)容: AJAX效斑、XMLHTTP非春、XMLHttpReq...
    semlinker閱讀 13,634評論 2 18
  • ??2005 年奇昙,Jesse James Garrett 發(fā)表了一篇在線文章,題為“Ajax: A new App...
    霜天曉閱讀 885評論 0 1
  • Ajax和XMLHttpRequest 我們通常將Ajax等同于XMLHttpRequest敌完,但細(xì)究起來它們兩個是...
    changxiaonan閱讀 2,229評論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理储耐,服務(wù)發(fā)現(xiàn),斷路器滨溉,智...
    卡卡羅2017閱讀 134,633評論 18 139