爬蟲——爬取Ajax動態加載網頁

常見的反爬機制及處理方式

一、Headers反爬蟲 :Cookie、Referer、User-Agenthtml

  解決方案: 經過F12獲取headers,傳給requests.get()方法python

二、IP限制 :網站根據IP地址訪問頻率進行反爬,短期內進制IP訪問mysql

  解決方案: sql

       一、構造本身IP代理池,每次訪問隨機選擇代理,常常更新代理池數據庫

       二、購買開放代理或私密代理IPjson

       三、下降爬取的速度api

三、User-Agent限制 :相似於IP限制網絡

  解決方案: 構造本身的User-Agent池,每次訪問隨機選擇app

五、對查詢參數或Form表單數據認證(salt、sign)dom

  解決方案: 找到JS文件,分析JS處理方法,用Python按一樣方式處理

六、對響應內容作處理

  解決方案: 打印並查看響應內容,用xpath或正則作處理

python中正則處理headers和formdata

一、pycharm進入方法 :Ctrl + r ,選中 Regex

二、處理headers和formdata

(.*): (.*)

 "$1": "$2",

三、點擊 Replace All

民政部網站數據抓取

目標: 抓取最新中華人民共和國縣以上行政區劃代碼

URL: http://www.mca.gov.cn/article/sj/xzqh/2019/ - 民政數據 - 行政區劃代碼

實現步驟

一、從民政數據網站中提取最新行政區劃代碼連接

  最新的在上面,命名格式: 2019年X月中華人民共和國縣以上行政區劃代碼

import requests
from lxml import etree
import re
​
url = 'http://www.mca.gov.cn/article/sj/xzqh/2019/'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'}
html = requests.get(url, headers=headers).text
parse_html = etree.HTML(html)
article_list = parse_html.xpath('//a[@class="artitlelist"]')
​
for article in article_list:
    title = article.xpath('./@title')[0]
    # 正則匹配title中包含這個字符串的連接
    if title.endswith('代碼'):
        # 獲取到第1個就中止便可,第1個永遠是最新的連接
        two_link = 'http://www.mca.gov.cn' + article.xpath('./@href')[0]
        print(two_link)
        break

二、從二級頁面連接中提取真實連接(反爬-響應網頁內容中嵌入JS,指向新的網頁連接)

  1. 向二級頁面連接發請求獲得響應內容,並查看嵌入的JS代碼
  2. 正則提取真實的二級頁面連接
# 爬取二級「假」連接
two_html = requests.get(two_link, headers=headers).text # 從二級頁面的響應中提取真實的連接(此處爲JS動態加載跳轉的地址) new_two_link = re.findall(r'window.location.href="(.*?)"', two_html, re.S)[0]

三、在數據庫表中查詢此條連接是否已經爬取,創建增量爬蟲

  1. 數據庫中創建version表,存儲爬取的連接
  2. 每次執行程序和version表中記錄覈對,查看是否已經爬取過
cursor.execute('select * from version')
result = self.cursor.fetchall()
if result:
   if result[-1][0] == two_link:
       print('已經是最新')
   else:
       # 有更新,開始抓取
       # 將連接再從新插入version表記錄

四、代碼實現

import requests
from lxml import etree
import re
import pymysql


class GovementSpider(object):
    def __init__(self):
        self.url = 'http://www.mca.gov.cn/article/sj/xzqh/2019/'
        self.headers = {'User-Agent': 'Mozilla/5.0'}
        # 建立2個對象
        self.db = pymysql.connect('127.0.0.1', 'root', '123456', 'govdb', charset='utf8')
        self.cursor = self.db.cursor()

    # 獲取假連接
    def get_false_link(self):
        html = requests.get(url=self.url, headers=self.headers).text
        # 此處隱藏了真實的二級頁面的url連接,真實的在假的響應網頁中,經過js腳本生成,
        # 假的連接在網頁中能夠訪問,可是爬取到的內容卻不是咱們想要的
        parse_html = etree.HTML(html)
        a_list = parse_html.xpath('//a[@class="artitlelist"]')
        for a in a_list:
            # get()方法:獲取某個屬性的值
            title = a.get('title')
            if title.endswith('代碼'):
                # 獲取到第1個就中止便可,第1個永遠是最新的連接
                false_link = 'http://www.mca.gov.cn' + a.get('href')
                print("二級「假」連接的網址爲", false_link)
                break
        # 提取真連接
        self.incr_spider(false_link)

    # 增量爬取函數
    def incr_spider(self, false_link):
        self.cursor.execute('select url from version where url=%s', [false_link])
        # fetchall: (('http://xxxx.html',),)
        result = self.cursor.fetchall()

        # not result:表明數據庫version表中無數據
        if not result:
            self.get_true_link(false_link)
            # 可選操做: 數據庫version表中只保留最新1條數據
            self.cursor.execute("delete from version")

            # 把爬取後的url插入到version表中
            self.cursor.execute('insert into version values(%s)', [false_link])
            self.db.commit()
        else:
            print('數據已經是最新,無須爬取')

    # 獲取真連接
    def get_true_link(self, false_link):
        # 先獲取假連接的響應,而後根據響應獲取真連接
        html = requests.get(url=false_link, headers=self.headers).text
        # 從二級頁面的響應中提取真實的連接(此處爲JS動態加載跳轉的地址)
        re_bds = r'window.location.href="(.*?)"'
        pattern = re.compile(re_bds, re.S)
        true_link = pattern.findall(html)[0]

        self.save_data(true_link)  # 提取真連接的數據

    # 用xpath直接提取數據
    def save_data(self, true_link):
        html = requests.get(url=true_link, headers=self.headers).text

        # 基準xpath,提取每一個信息的節點列表對象
        parse_html = etree.HTML(html)
        tr_list = parse_html.xpath('//tr[@height="19"]')
        for tr in tr_list:
            code = tr.xpath('./td[2]/text()')[0].strip()  # 行政區劃代碼
            name = tr.xpath('./td[3]/text()')[0].strip()  # 單位名稱

            print(name, code)

    # 主函數
    def main(self):
        self.get_false_link()


if __name__ == '__main__':
    spider = GovementSpider()
    spider.main()

動態加載數據抓取-Ajax

特色

  1. 右鍵 -> 查看網頁源碼中沒有具體數據
  2. 滾動鼠標滑輪或其餘動做時加載

抓取

  1. F12打開控制檯,選擇XHR異步加載數據包,找到頁面動做抓取網絡數據包
  2. 經過XHR-->Header-->General-->Request URL,獲取json文件URL地址
  3. 經過XHR-->Header-->Query String Parameters(查詢參數)

豆瓣電影數據抓取案例

目標

  1. 地址: 豆瓣電影 - 排行榜 - 劇情
  2. 目標: 爬取電影名稱、電影評分

F12抓包(XHR)

一、Request URL(基準URL地址) :https://movie.douban.com/j/chart/top_list?

二、Query String Paramaters(查詢參數)

# 查詢參數以下:
type: 13 # 電影類型
interval_id: 100:90
action: '[{},{},{}]'
start: 0  # 每次加載電影的起始索引值
limit: 20 # 每次加載的電影數量

json文件在如下地址:

基準URL地址+查詢參數

'https://movie.douban.com/j/chart/top_list?'+'type=11&interval_id=100%3A90&action=&start=20&limit=20'

代碼實現

import requests
import time
from fake_useragent import UserAgent


class DoubanSpider(object):
    def __init__(self):
        self.base_url = 'https://movie.douban.com/j/chart/top_list?'
        self.i = 0

    def get_html(self, params):
        headers = {'User-Agent': UserAgent().random}
        res = requests.get(url=self.base_url, params=params, headers=headers)
        res.encoding = 'utf-8'
        html = res.json()  # 將json格式的字符串轉爲python數據類型
        self.parse_html(html)  # 直接調用解析函數

    def parse_html(self, html):
        # html: [{電影1信息},{電影2信息},{}]
        item = {}
        for one in html:
            item['name'] = one['title']  # 電影名
            item['score'] = one['score']  # 評分
            item['time'] = one['release_date']  # 打印測試
            # 打印顯示
            print(item)
            self.i += 1

    # 獲取電影總數
    def get_total(self, typ):
        # 異步動態加載的數據 均可以在XHR數據抓包
        url = 'https://movie.douban.com/j/chart/top_list_count?type={}&interval_id=100%3A90'.format(typ)
        ua = UserAgent()
        html = requests.get(url=url, headers={'User-Agent': ua.random}).json()
        total = html['total']

        return total

    def main(self):
        typ = input('請輸入電影類型(劇情|喜劇|動做):')
        typ_dict = {'劇情': '11', '喜劇': '24', '動做': '5'}
        typ = typ_dict[typ]
        total = self.get_total(typ)  # 獲取該類型電影總數量

        for page in range(0, int(total), 20):
            params = {
                'type': typ,
                'interval_id': '100:90',
                'action': '',
                'start': str(page),
                'limit': '20'}
            self.get_html(params)
            time.sleep(1)
        print('爬取的電影的數量:', self.i)


if __name__ == '__main__':
    spider = DoubanSpider()
    spider.main()

 

騰訊招聘數據抓取(Ajax)

肯定URL地址及目標

要求與分析

  1. 經過查看網頁源碼,得知所需數據均爲 Ajax 動態加載
  2. 經過F12抓取網絡數據包,進行分析
  3. 一級頁面抓取數據: 職位名稱
  4. 二級頁面抓取數據: 工做職責、崗位要求

一級頁面json地址(pageIndex在變,timestamp未檢查)

https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn

二級頁面地址(postId在變,在一級頁面中可拿到)

https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn

useragents.py文件

ua_list = [
  'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1',
  'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0',
  'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)',
]
import time
import json
import random
import requests
from useragents import ua_list


class TencentSpider(object):
    def __init__(self):
        self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
        self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
        self.f = open('tencent.json', 'a')  # 打開文件
        self.item_list = []  # 存放抓取的item字典數據

    # 獲取響應內容函數
    def get_page(self, url):
        headers = {'User-Agent': random.choice(ua_list)}
        html = requests.get(url=url, headers=headers).text
        html = json.loads(html)  # json格式字符串轉爲Python數據類型

        return html

    # 主線函數: 獲取全部數據
    def parse_page(self, one_url):
        html = self.get_page(one_url)
        item = {}
        for job in html['Data']['Posts']:
            item['name'] = job['RecruitPostName']  # 名稱
            post_id = job['PostId']  # postId,拿postid爲了拼接二級頁面地址
            # 拼接二級地址,獲取職責和要求
            two_url = self.two_url.format(post_id)
            item['duty'], item['require'] = self.parse_two_page(two_url)
            print(item)
            self.item_list.append(item)  # 添加到大列表中

    # 解析二級頁面函數
    def parse_two_page(self, two_url):
        html = self.get_page(two_url)
        duty = html['Data']['Responsibility']  # 工做責任
        duty = duty.replace('\r\n', '').replace('\n', '')  # 去掉換行
        require = html['Data']['Requirement']  # 工做要求
        require = require.replace('\r\n', '').replace('\n', '')  # 去掉換行

        return duty, require

    # 獲取總頁數
    def get_numbers(self):
        url = self.one_url.format(1)
        html = self.get_page(url)
        numbers = int(html['Data']['Count']) // 10 + 1  # 每頁有10個推薦

        return numbers

    def main(self):
        number = self.get_numbers()
        for page in range(1, 3):
            one_url = self.one_url.format(page)
            self.parse_page(one_url)

        # 保存到本地json文件:json.dump
        json.dump(self.item_list, self.f, ensure_ascii=False)
        self.f.close()


if __name__ == '__main__':
    start = time.time()
    spider = TencentSpider()
    spider.main()
    end = time.time()
    print('執行時間:%.2f' % (end - start))
相關文章
相關標籤/搜索