用Python向博客園發佈新文章

  最近在開發一個博客系統,常常把寫的東西放在本身網站的博客上(以前寫在Onenote),而後我在博客園也申請了一個博客,就有了一樣一篇文章,我須要複製粘貼排版分別提交兩次的狀況。因而我就想能不能在個人網站內提交後直接把這篇文章同步提交至博客園甚至是其餘第三方博客呢,因此花點時間實現了這個功能。本文寫的比較細,面向對這一塊瞭解很少的同窗,大神就付之一笑吧。html

 

1、分析HTTP請求

  全部瀏覽器行爲,本質都是向web服務器發起http請求,服務器收到請求後,根據請求內容返回結果,瀏覽器通過渲染後最終呈現給用戶。python

  登陸博客園,進入後臺,新建一篇隨筆,能夠看到,編輯頁面的url爲https://i.cnblogs.com/EditPosts.aspx?opt=1,把標題和內容隨便寫一寫,F12打開Chrome控制檯,因爲文章發佈後博客園會有重定向,因此把Preserver log勾選上,這樣刷新網頁歷史記錄也不會消失。web

  點擊發布按鈕,出來一大堆東西瀏覽器

   第一個結果看名字推測是驗證用戶是否登陸,咱們直接點第二個結果:服務器

  發現這就是一個常規的POST請求,顯然這大機率是咱們要找的目標,繼續看看它提交了什麼數據cookie

  除了圖片上的字段,還有一段很長的字段,字段名爲__VIEWSTATEsession

  能夠看到,除了__VIEWSTATE和__VIEWSTATEGENERATOR咱們徹底不知道是什麼以外,下面幾個字段看名字就能夠推測做用post

  咱們先無論具體的做用,注意到POST請求的url和咱們編輯文章的url是同一個地址,推測這裏直接使用form表單提交的可能性較大,回到頁面看看http結構測試

 

 

  在頁面中確實找到了form表單,而且下面剛好就有一個隱藏input,就是咱們剛纔看到的__VIEWSTATE。網站

  肯定了是form表單後,事情就變得簡單了,找到並確認提交的字段做用以下:

  __VIEWSTATE:博客園生成字段

  __VIEWSTATEGENERATOR:博客園生成字段

  Editor$Edit$txbTitle:文章標題

  Editor$Edit$EditorBody:文章內容

  Editor$Edit$Advanced$txbTag:文章標籤

  Editor$Edit$Advanced$txbExcerpt:文章摘要

  Editor$Edit$Advanced$ckbPublished:on   文章是否發佈

  Editor$Edit$Advanced$chkDisplayHomePage:on  顯示在個人博客首頁

  Editor$Edit$Advanced$chkComments:on  容許評論

  Editor$Edit$Advanced$chkMainSyndication:on  顯示在RSS中

  Editor$Edit$Advanced$chkPinned:on  置頂

  Editor$Edit$Advanced$txbEntryName:友好地址名

  Editor$Edit$Advanced$rblPostType:文章類型 (1-隨筆 2-文章 3-新聞 4-日記)

  Editor$Edit$Advanced$tbEnryPassword:閱讀密碼

  Editor$Edit$lkbPost:發佈

  這些就是主要字段,值得注意的是Editor$Edit$lkbPost的值,能夠是「發佈」,也能夠是「存爲草稿」,功能就不言自明瞭

  分析完提交文章的請求過程,再來看看博客園的響應內容:

 

  響應狀態碼爲302,表明頁面重定向,重定向到localtion的地址,這裏地址有個值得注意點,就是postid=11510913,不出所料是新文章的id,後續可能會有用。

好了,說了這麼一圈,其實整個http請求異常簡單:

  用戶使用POST方式向https://i.cnblogs.com/EditPosts.aspx?opt=1提交數據,若是成功會返回一個重定向的地址,這個地址包含了一個新文章的id。下面開始用代碼來實現吧。

2、 模擬登陸

  雖然在分析HTTP請求的過程當中一直沒有談到登陸,但博客園確定是要在登陸狀態下才能發文的,一般能夠採用兩種方式來實現模擬用戶登陸行爲。

2.1 基於Cookie

  何爲Cookie?能夠舉一個並不十分恰當的例子。咱們去高鐵站坐高鐵,要通過取票、刷票進站這麼一個流程,閘機會經過驗證高鐵票的真僞、出行時間、人臉認證來判斷是否放行。在這個例子中,高鐵票就是Cookie,web服務器首先在咱們登陸時給了咱們一個Cookie(取票),而後咱們下次訪問頁面時就會帶着這個Cookie一塊兒提交請求(驗票),服務器一看,哦這傢伙帶着我給它發的通行證,再一瞧通行證是否是假的,有沒有過時,驗證後都沒問題就能夠知道是哪個用戶在訪問它,進而給用戶提供相應的服務。

  瞭解Cookie以後,咱們就知道這是服務器發的身份證,咱們只要在訪問頁面時候把Cookie一塊兒帶上,服務器就會認爲你已經登陸了。那麼如何拿到Cookie呢,其實Cookie就在HTTP的請求頭裏面:

 

  很長的一段,不要緊所有複製出來確定不會錯。

  下面開始咱們的第一段代碼

import requests

def get_login_session(cookie):
    headers = {
        'referer': 'https://i.cnblogs.com/',
        'cookie': cookie
    }
    session = requests.session()
    session.headers.update(headers)
    return session

  get_login_session方法接收一個cookie,返回一個session,其實session就是requests的另外一層封裝,它會自動把你處理像Cookie呀一類的請求。咱們在這個方法內給session傳遞了兩個請求頭,一個是cookie,另外一個是referer,cookie就不用多說了,referer是因爲很多網站會用這個字段來判斷你是否是機器人,出於經驗主義我把它加上來了,可是若是不加是否有效,大家能夠自行驗證一下。

  若是對session甚至是requests還有疑問的同窗,能夠查閱官方文檔http://2.python-requests.org/zh_CN/latest/user/advanced.html#advanced

2.2 帳號密碼登陸

  使用Cookie模擬登陸,在代碼層面來看確實十分簡單,可是對於普通用戶來講,他未必可以理解Cookie並找到它,更多人能記住的僅僅是本身的帳號密碼,因此理應要有帳號密碼登陸的功能。若是你理解了本文的第一部分,就會發現登陸本質上仍是一個POST請求,並且更簡單、提交的字段更少。須要特別說明的一點是,博客園有一個驗證機制,登陸的時候大機率會彈出一個滑塊驗證碼,只有驗證經過後纔會讓你登陸。針對這個問題,以個人認知,requests目前是沒有辦法解決的,但真的要作,也不是全無辦法,咱們能夠採用selenium來實現模擬登陸,過滑動驗證碼的方案百度上也有不少(本想貼我之前看過的一篇文章,無奈沒找到~),模擬拿到Cookie後便可,這裏我就不詳講了,若是你們確實感興趣,後續我在專門寫一篇過博客園驗證碼的文章。

3、 requests構建HTTP請求

 如今咱們拿到登陸後的session,要作的只是提交一篇新文章的POST請求,先上代碼

from bs4 import BeautifulSoup

def post_article(session,title,summary,content,**kwargs):
    '''
    向博客園提交新文章
    :param session:登陸的session
    :param title: 文章標題
    :param summary: 文章摘要
    :param content: 文章內容
    :param kwargs: 自定義form表單內容
    :return: Response
    '''
    url = 'https://i.cnblogs.com/EditPosts.aspx?opt=1'
    wb_data = session.get(url,allow_redirects=False)
    soup = BeautifulSoup(wb_data.txt,'lxml')
    __VIEWSTATE = soup.find(id='__VIEWSTATE')['value']
    __VIEWSTATEGENERATOR = soup.find(id='__VIEWSTATEGENERATOR')['value']
    data = {'Editor$Edit$lkbPost': '',
            'Editor$Edit$Advanced$ckbPublished': 'on',
            'Editor$Edit$Advanced$chkDisplayHomePage': 'on',
            'Editor$Edit$Advanced$chkComments': 'on',
            'Editor$Edit$Advanced$chkMainSyndication': 'on',
            'Editor$Edit$Advanced$txbEntryName': '',
            'Editor$Edit$Advanced$txbExcerpt': summary,
            'Editor$Edit$Advanced$txbTag': '',
            'Editor$Edit$Advanced$tbEnryPassword': '',
            '__VIEWSTATE': __VIEWSTATE,
            '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
            'Editor$Edit$txbTitle': title,
            'Editor$Edit$EditorBody': content}
    data.update(kwargs)
    response = session.post(url,data=data,allow_redirects=False)
    return response

  代碼內的註釋應該很明白了,額外說幾點。第一點是因爲__VIEWSTATE和__VIEWSSTATEGENERATOR字段是博客園生成的,因此我首先是用get請求,使用BeautifulSoup解析返回頁面並找到__VIEWSTATE和__VIEWSSTATEGENERATOR,而後再構建data進行post提交。第二個點是因爲先前咱們已經注意到,返回的是一個302重定向頁面,而requests是默認自動幫咱們作重定向的,因爲咱們在後續的步驟中須要最原始的響應來幫助咱們做判斷,因此咱們使用allow_redirects=False禁用了重定向。最後一點是post_article方法還支持以鍵值對的方式傳遞任意參數,這些參數最終會更新到data並提交至博客園,因此咱們能夠在調用方法時控制提交文章的一些選項,好比post_article(session,title,summary,content,Editor$Edit$Advanced$txbTag="Python")。

4、 獲取判斷返回結果

  實際上,通常狀況下調用post_article方法的後你的文章已經發布出去了,若是你想判斷是否真的成功了,那麼咱們能夠繼續。 

  在第一部分咱們知道了若是發佈文章成功,那麼服務器首先返回的是一個狀態碼爲302的重定向頁面,若是發佈失敗了,好比當我發表標題重複的文章又或者觸碰了其餘博客園規則,這時候服務器返回的就是一個狀態碼爲200的普通頁面。因此咱們能夠根據返回對象的status或者Localtion來作一層判斷

location = response.headers.get('Location')
if location:
    return True

5、其餘

  值得一提的是,博客園文章的內容是基於html語言的,若是直接把普通文本提交到博客園,那麼文章的排版確定會十分混亂,因此對文章內容須要進行特別處理,因爲我在寫的博客系統,存儲的文章內容自己就是基於html語言的,因此我這也就沒有處理需求,在本文就不展開講了。

  新建文章也不只僅只有我列出的那一部分字段,若是我沒有列出來的,能夠在form表單下的input標籤。

5、完整示例

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
import requests

def get_login_session(cookie):
    headers = {
        'referer': 'https://i.cnblogs.com/',
        'cookie': cookie
    }
    session = requests.session()
    session.headers.update(headers)
    return session

def post_article(session,title,summary,content,**kwargs):
    '''
    向博客園提交新文章
    :param session:登陸的session
    :param title: 文章標題
    :param summary: 文章摘要
    :param content: 文章內容
    :param kwargs: 自定義form表單內容
    :return: Response
    '''
    url = 'https://i.cnblogs.com/EditPosts.aspx?opt=1'
    wb_data = session.get(url,allow_redirects=False)
    soup = BeautifulSoup(wb_data.text,'lxml')
    __VIEWSTATE = soup.find(id='__VIEWSTATE')['value']
    __VIEWSTATEGENERATOR = soup.find(id='__VIEWSTATEGENERATOR')['value']
    data = {'Editor$Edit$lkbPost': '',
            'Editor$Edit$Advanced$ckbPublished': 'on',
            'Editor$Edit$Advanced$chkDisplayHomePage': 'on',
            'Editor$Edit$Advanced$chkComments': 'on',
            'Editor$Edit$Advanced$chkMainSyndication': 'on',
            'Editor$Edit$Advanced$txbEntryName': '',
            'Editor$Edit$Advanced$txbExcerpt': summary,
            'Editor$Edit$Advanced$txbTag': '',
            'Editor$Edit$Advanced$tbEnryPassword': '',
            '__VIEWSTATE': __VIEWSTATE,
            '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
            'Editor$Edit$txbTitle': title,
            'Editor$Edit$EditorBody': content}
    data.update(kwargs)
    response = session.post(url,data=data,allow_redirects=False)
    return response

if __name__ == "__main__":
    cookie = input('請輸入博客園Cookie: ')
    session = get_login_session(cookie)
    response = post_article(session,'測試標題','測試摘要','測試內容')
    location = r.headers.get('Location')
    if location:
        print('文章發佈成功')
    else:
        soup = BeautifulSoup(r.text, 'lxml')
        ErrorPanel = soup.find('div', {'class': 'ErrorPanel'})
        if ErrorPanel:
            print(ErrorPanel.get_text())
        print('文章發佈失敗')
相關文章
相關標籤/搜索