基於 Python 的網絡爬蟲:獲取異步加載的數據

1. 需求分析

    從重慶市政府採購網自動獲取所有的採購公告信息,將其項目名稱和採購物資通過可讀的方式展示。

2. 實現過程

  • 分析頁面佈局重慶市政府採購網採購公告

  • 第一次爬取到“假網址”

(1)首先,展示第一次爬取到的“假網址”。通過 xpath 匹配該 div。
通過 xpath 匹配的結果
(2)嘗試採集當前頁面的所有二級鏈接。

import requests
from lxml import etree
import json


def getpage(url, headers):
    res = requests.get(url, headers=headers)
    html = etree.HTML(res.text)
    return html


def parsepage(url, headers, all):
    html = getpage(url, headers)
    urllist = html.xpath("//div[@class='list-group-item ng-scope']/div[@class='row']//a")
    print(urllist)
    for i in urllist:
        url = i.xpath("./@href")
        all.append(url)


if __name__ == "__main__":
    all = []
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
        (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36",
    }
    url = "https://www.ccgp-chongqing.gov.cn/notices/list"
    res = parsepage(url, headers, all)
    with open("ztb.html", "w", encoding="utf-8") as f:
        f.write(res)

提示報錯:TypeError: write() argument must be str, not None

可以看出,這種方法存在問題!
我們可以將使用 request 方法獲取的數據保存爲 html 文件,查看內容。
重新寫一個只保存首頁信息的函數:

import requests
from lxml import etree
import json


def getpage(url, headers):
    res = requests.get(url, headers=headers)
    # html = etree.HTML(res.text)
    return res.text


if __name__ == "__main__":
    all = []
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
        (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36",
    }
    url = "https://www.ccgp-chongqing.gov.cn/notices/list"
    res = getpage(url,headers)
    with open("bidding.html", "w", encoding="utf-8") as f:
        f.write(res)

查看 bidding.html 文件:
bidding.html
通過查看獲取的內容,發現沒有任何目錄信息!得知這種方法採集的數據並不完整!
雖然想要的內容都在網頁上,卻爬取不到任何信息。這裏我們考慮到網頁異步加載的情況。
於是我們使用谷歌開發者工具檢查 JS 和 XHR 中的每一項,發現在 XHR 中有我們想要的數據!
真urls
我們不妨請求一下這個網址,看看返回的是什麼數據?
其實是一種 JSON 格式的數據,還是通過 JSON 格式顯示工具來查看一下!
所有公告信息

  • 第二次爬取“真網址”

(1)在採集所有二級鏈接之前,我們先分析一下每個 url 的格式。
單個公告
每一個公告的域名都有三部分構成,即域名、目錄和參數。其中域名都是同樣的;目錄部分結尾是由每個項目的 id 構成的;參數是在"?"後面,可以不傳。
(2)分析完之後,我們開始對新的 url 發起請求。
保留每個公告的項目名稱和 url 數據,並且輸出爲 JSON 文本:

import requests
import json


def getpage(url, headers):
    response = requests.get(url, headers=headers)
    html = response.json()
    return html


def getnotice(url, headers):
    allnotices = []
    html = getpage(url, headers)
    notices = html['notices']
    for i in notices:
        notice = {}
        notice["title"] = i["title"]
        notice["id"] = i["id"]
        allnotices.append(notice)
    return allnotices


def geturl(url, headers):
    allnotices = getnotice(url, headers)
    allurls = []
    for j in allnotices:
        everyurl = {}
        everyurl["projectname"] = j["title"]
        everyurl["url"] = "https://www.ccgp-chongqing.gov.cn/notices/detail/" + j["id"]
        everyurl["id"] = j["id"]
        allurls.append(everyurl)
    return allurls


if __name__ == "__main__":
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \
        Chrome/63.0.3239.132 Safari/537.36"
    }
    url = "https://www.ccgp-chongqing.gov.cn/gwebsite/api/v1/notices/stable/new?endDate=2020-03-25&pi=1&ps=100&startDate=2019-12-26"
    allurls = geturl(url, headers)
    f = open("urls.json", "w", encoding="utf-8")
    f.write(json.dumps(allurls, ensure_ascii=False, indent=4) + "\n")
    f.close()

打開 allurls.json 文件,查看我們獲取的數據是否是正確的。
allurls

  • 採集每個公告的項目信息和採購物資

(1)辨別“真假”。
在採集公告內容之前,需要特別說明的是,每個公告的內容其實也是通過異步傳輸的方式,我們可以通過谷歌開發工具檢測。
真url
同樣,我們把它放在 JSON 格式顯示工具裏面查看一下。
單個公告的內容
(2)開始採集滿足需求的“真網址”。
我們最後想要的數據都在 title 和 purchaseDes 當中。我們在採集這部分數據之前,需要過濾掉不是"採購公告"的數據,另外添加一行 trueurl 的數據。

import json


# 讀取過濾之前的數據
def getdata():
    f = open(r"F:./urls.json", encoding="utf-8")
    data = f.read()
    f.close()
    urls = json.loads(data)
    return urls


# 過濾數據
def getgrepurls(grepurls):
    urls = getdata()
    for p in urls:
        if p["projectname"][-4:] == "採購公告" :
            p["trueurl"] = "https://www.ccgp-chongqing.gov.cn/gwebsite/api/v1/notices/stable/" + p["id"]
            grepurls.append(p)

if __name__ == "__main__":
    grepurls = []
    getgrepurls(grepurls)
    f = open("grepurls.json", "w", encoding="utf-8")
    f.write(json.dumps(grepurls, ensure_ascii=False, indent=4) + "\n")
    f.close()

打開 grepurls.json 文件,檢查過濾的結果。

grepurls
(3)最後開始爬取每個公共的項目名稱和採購物資。
特別注意的是,第一次過濾後得到的雖然都是採購公告,但是部分採購公告的頁面佈局不同,不包含採購物資的內容。因此還需把這部分數據剔除!

import requests
import json


# 讀過濾之前的數據
def getdata1():
    f = open(r"F:./grepurls.json", encoding="utf-8")
    data = f.read()
    f.close()
    grepurls = json.loads(data)
    return grepurls


# 第二次過濾,過濾不包含分包內容的採購公告
def getnewurls(headers):
    grepurls = getdata1()
    newurls = []
    for i in grepurls:
        trueurl = i["trueurl"]
        response = requests.get(trueurl, headers=headers)
        trueurldata = response.content.decode()
        json_trueurldata = json.loads(trueurldata)
        try:
            purchaseDes = json_trueurldata["notice"]["purchaseDes"]
        except KeyError as e:
            print("%s不包含分包內容信息!"%i["projectname"])
        else:
            newurls.append(i)
    return newurls


# 讀第二次過濾的數據
def getdata2():
    f = open(r"F:./newurls.json", encoding="utf-8")
    data = f.read()
    f.close()
    newurls = json.loads(data)
    return newurls


# 保存項目名稱和採購物資
def getcontent(headers):
    newurls = getdata2()
    contents = []
    for i in newurls:
        good = ""
        trueurl = i["trueurl"]
        response = requests.get(trueurl, headers=headers)
        trueurldata = response.content.decode()
        json_trueurldata = json.loads(trueurldata)
        purchaseDes = json_trueurldata["notice"]["purchaseDes"]
        json_purchaseDes = json.loads(purchaseDes)
        for p in json_purchaseDes:
            content = {}
            content["projectname"] = i["projectname"]
            for g in p["good"]:
                good = g["title"] + "," + good
            content["good"] = good
            print(content)
        contents.append(content)
    return contents


if __name__ == "__main__":
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
    }
    newurls = getnewurls(headers)
    f = open("newurls.json", "w", encoding="utf-8")
    f.write(json.dumps(newurls, ensure_ascii=False, indent=4) + "\n")
    f.close()
    contents = getcontent(headers)
    f = open("contents.json", "w", encoding="utf-8")
    f.write(json.dumps(contents, ensure_ascii=False, indent=4) + "\n")
    f.close()

打開 contents.json 文件,查看最後的結果。
contents

3. 結束語

    本系統是博主第一個完整開發的爬蟲系統,系統中還存在有待改進的地方,例如:指定爬取發佈日期區間的公共,數據的展示效果略微粗糙,數據還需提純等。希望日後加以完善。

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