【2】爬蟲學習

這篇博客主要是從具體的項目入手,通過項目來學習相關的爬蟲技術,以做代學。

分析Ajax請求抓取今日頭條街拍美圖

前提概念補充

​ 有時候我們在用request抓取頁面的時候,得到的結果和瀏覽器中看到的可能不一樣,在瀏覽器中看到的是正常顯示的頁面數據,但是在request得到的結果並沒有。這是因爲獲取得到的都是原始的HTML文檔,但是瀏覽器頁面是經過 JavaScript處理數據後生成的結果。這些數據可能是通過Ajax加載得到的。

​ 對於Ajax加載數據的情況來說,數據加載是一種異步加載的方式,原始的頁面最初不會包含某些數據,原始頁面加載完成之後,會再向服務器請求某個接口獲取數據。然後數據纔會被處理從而呈現在網頁上。這其實就是發送了一個Ajax請求。現在網頁發展就是這樣:網頁原始的HTML文檔不包含任何數據,數據都是通過Ajax統一加載之後呈現出來的,這樣就可以在Web開發商可以做前後端分離,而且降低服務器直接渲染頁面帶來的壓力。

​ 如果遇到這樣的頁面是無法獲取有效數據的,這時就需要分析網頁後臺向接口發送的Ajax請求,如果可以用requests來模擬Ajax請求,那麼就可以成功抓取了。

  • Ajax

在說這個項目之前,一般的爬蟲小白都會對這個標題中的Ajax產生疑問需要先解釋一下AjaxAjax就是Asynchronous JavaScript and XML(異步的JavaScriptXML技術)說白了就是在不關閉不跳轉不刷新的情況下,在網頁後臺提交數據,部分更新頁面的內容。在這個過程中,頁面實際上是在後臺與服務器進行了數據交互,獲取到數據之後,再利用JavaScript改變網頁,這樣網頁內二就會更新了。

舉例說明:我們查看微博的時候,都會看到查看了一定版面的微博之後,會出現一個加載的動畫,過一會之後會出現新的微博。這個加載的過程就是Ajax加載。

Ajax請求步驟:

  • 發送請求
  • 解析內容
  • 渲染網頁
  • JSON

還有一個需要介紹的概念(別怪我囉嗦,因爲我也是小白,對web知識瞭解的也不多,邊做邊學)是JSON,這個學術上的名稱是JavaScript Object Notation,本質上說他是一種傳遞對象的語法,對象可以是name/value鍵值對,數組和其他對象。

主要組成是:{}、[]、:、,

  • pycharm 相同目錄下引用其他文件

pycharm不會將當前文件目錄自動加入自己的source_path.右鍵make_directory as->source path 將當前工作的文件夾加入 source_path就可以了。

項目結構

​ 好了,之前我們說到了真實數據是Ajax請求得到的,如果想抓取這些數據,需要知道這些請求時怎麼發送的,發送到哪裏,發了哪些參數。請看Ajax分析:

  1. 查看請求

    在微博界面上按F12鍵,便可以彈出開發者模式。在Element選項卡中可以查看網頁源代碼,點擊Network選項卡,之後再重新刷新頁面,可以發現有很多條目,這個就是頁面加載過程中瀏覽器與服務器之間發送請求和接受響應的所有記錄。

    在這些記錄裏面查找Ajax請求的方法:我們可以在getIndex開頭的信息裏面找到一個,其Typexhr的請求類型,這就是Ajax

  2. 過濾請求

    瀏覽器裏按F12出現開發者工具篩選功能篩選出所有的Ajax請求,在請求上方有一層篩選欄,直接點擊XHR,此時下方顯示的所有請求就是 Ajax請求了。向下滑動網頁,可以看到有新的Ajax請求出現。

這個項目說實話是一個比較基本的爬蟲練手項目,目的是爬取今日頭條上的美女圖片。主要的流程框架是:

  • 抓取索引頁內容

    利用request請求目標站點,得到索引網頁HTML代碼,返回結果。

  • 抓取詳情頁內容

    解析返回結果,得到詳情頁的鏈接,並進一步抓取詳情頁的信息

  • 開啓循環及多線程

    對多頁內容遍歷,開啓多線程提高抓取速度

  • 下載圖片與保存數據庫

    將圖片下載到本地,並把頁面信息及圖片URL保存至MongoDB

好了,介紹完大體架構了,然後就要開始實操了。我用的是pycharm來寫python代碼的。界面比較友好,這裏推薦一下。然後再介紹一下pycharm的快捷鍵,方便大家快速便捷地寫出優雅的代碼。
Pycharm Windows快捷鍵

代碼分析

代碼文件總共是兩個,一個是 spider.py,一個是 config.py文件。

# 在URL中沒有相應的搜索字段,可以判斷網頁內容是`Ajax`加載,然後用`JavaScript`渲染出來的,接下來切
#換到 XHR過濾選項卡,可以查看`Ajax`請求。可以看到請求是屬於GET請求的,請求URL的參數有offset,#format,keyword,autoload,count和cur_tab 需要找出這些參數的規律,這樣才能方便地構造程序。
import json
import os
from urllib.parse import urlencode
import pymongo
import requests
from bs4 import BeautifulSoup
from requests.exceptions import ConnectionError
import re
from multiprocessing import Pool
from hashlib import md5
from json.decoder import JSONDecodeError
from config import *

#用來和MongoDB數據庫進行連接
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]


#獲取URL內容
def get_page_index(offset, keyword):
    data = {
        'autoload': 'true',
        'count': 20,
        'cur_tab': 3,
        'format': 'json',
        'keyword': keyword,
        'offset': offset,
    }
    params = urlencode(data)
    base = 'http://www.toutiao.com/search_content/'
    url = base + '?' + params
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None

# 下載圖片
def download_image(url):
    print('Downloading', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except ConnectionError:
        return None

# 保存圖片
def save_image(content):
    file_path = '{0}/{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
    print(file_path)
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)
            f.close()

# 解析導航頁
def parse_page_index(text):
    try:
        data = json.loads(text)
        if data and 'data' in data.keys():
            for item in data.get('data'):
                yield item.get('article_url')
    except JSONDecodeError:
        pass

# 獲取詳情頁
def get_page_detail(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None

# 解析詳情頁
def parse_page_detail(html, url):
    soup = BeautifulSoup(html, 'lxml')
    result = soup.select('title')
    title = result[0].get_text() if result else ''
    images_pattern = re.compile('gallery: JSON.parse\("(.*)"\)', re.S)
    result = re.search(images_pattern, html)
    if result:
        data = json.loads(result.group(1).replace('\\', ''))
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images = [item.get('url') for item in sub_images]
            for image in images: download_image(image)
            return {
                'title': title,
                'url': url,
                'images': images
            }

# 存儲到數據庫
def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('Successfully Saved to Mongo', result)
        return True
    return False


def main(offset):
    text = get_page_index(offset, KEYWORD)
    urls = parse_page_index(text)
    for url in urls:
        html = get_page_detail(url)
        result = parse_page_detail(html, url)
        if result: save_to_mongo(result)

# 這裏定義了分頁的起始頁數和終止頁數,分別GROUP_START 和 GROUP_END,利用了多線程的線程池,並調用#`map`函數實現多線程下載
if __name__ == '__main__':
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(main, groups)
    pool.close()
    pool.join()
MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'

GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'

參考文獻

你有哪些想要分享的 PyCharm 使用技巧?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章