iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 7
11

定義目標

參加 IT 鐵人賽目前已經進入第七天了,發現其實產文章不是最難的,反而每天都必須撥時間在電腦前才是最困難的,我都沒辦法不帶電腦出遠門了QQ。既然如此,那我們就寫個排程讓系統自己每天發文章,我只需要把文章準備好就好了。


實際探訪

IT 鐵人賽要發一篇文章的流程其實不困難,大致流程就四個動作

  1. 點選「鐵人發文」
  2. 點選主題
  3. 填入標題和內文
  4. 點選發表文章

create request
而在這些動作流程跑下來,發現在第二步驟「點選主題」時產生了一個 create 的 request,然後我們被導向一個帶有類似 article id 的頁面

publish request
接著在點選第四步驟「發表文章」時,發現他又送了一個關鍵的 publish request 出去,裡頭帶著文章標題、文章內容、tags,還有兩個神秘的參數 _token 和 _method,接著就發文成功了,可見我們只要搞定這兩個 request,那麼應該就大功告成了。

ps. 這兩個神秘參數對網站來說有其意義,所以並不能省略,一個是發文的 token 驗證,一個是 request 的 method,因為 browser 其實無法容易的送出 put 和 delete method。

分解研究

驗證身份

但在繼續之前,我們忘記了一件事情,就是肯定要先登入才能夠發文呀。在 Day 4 爬蟲原則和技巧中有提到,對於 http 來說,每次的 request 都是 stateless,也就是說,在這個 request 裡面肯定有某樣東西是驗證我們為已登入狀態。易地而處的思考一下,若我們是網頁工程師,那麼若要驗證使用者,肯定第一個會想到的就是 session 和 cookie。

check cookie for login
所以直覺的就來測試看看把 cookie 加上去,看起來是可以確保是登入狀態,同時我們檢查一下 cookie 的有效期限有 30d,看起來挺夠的。

建立文章

模擬 create post
接下來我們模擬 create 文章的 request,同時帶上 cookie,然後我們得到一個帶有 article id 的 302 redirect response。

發表文章

模擬 publish post
接著我們對這個 article id 做 publish request,一樣帶上 cookie 還有事先準備好的文章內容外加那兩個神秘的參數 _token 和 _method,送出後我們就看到成功的 response,搞定!

實作程式碼

備料

先把準備好的 cookie、post subject、post description、tags 拿起來備用。

// 請自行使用自己的 cookie,小心不要放在公開的地方…
var cookie = 'xxxxxx';
var postContent = {
    subject: '預先準備好的標題',
    description: '預先準備好的內容',
    tags: ['test1', 'test2'],
};

createPost function

接下來實作 createPost function,這個 get request 不用帶任何參數,只要一個固定的網址(網址每個人都不一樣,請看自己的建立主題文章的網址),不過在 options 裡面我加上 followRedirect: false,因為這個 request response 會直接 302 redirect,為了比較好抓 response 裡面的 article ID,所以我停止自動 redirect,然後我用 regex 將抓取到的 article ID 丟給 callback。

ps. 在使用 regex expression 的時候,我常用 https://regex101.com/ 來做測試

function createPost(callback) {

  var options = {
    url: 'https://ithelp.ithome.com.tw/articles/create?group=tech',
    followRedirect: false,
    headers: {
      'cookie': cookie
    }
  };

  request(options, (err, res, body) => {
    var articleId = body.match(/articles\/(.+)\/draft/)[1];
    callback(articleId);
  })
}

publishPost function

然後我們就準備來發文章了,發文章只需要一個 post request,這裡 post 過去的 url 會包含剛剛得到的 article ID,同時帶上觀察到的 form 的參數,然後再放個 callback 驗證一下 request 完成。

function publishPost(articleId, postContent, callback) {

  var options = {
    url: `https://ithelp.ithome.com.tw/articles/${articleId}/publish`,
    followRedirect: false,
    method: 'POST',
    headers: {
      'Cookie': cookie,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    form: {
      // 請自行觀察 token 內容
      '_token': 'xxxxxx',
      'group': 'tech',
      '_method': 'PUT',
      'subject': postContent.subject,
      'description': postContent.description,
      'tags': postContent.tags,
    },
  };

  request(options, (err, res, body) => {
    callback()
  })
}

積木組合

我們完成了所有元件,接下來就來組合流程吧,首先先 create post,取得 article ID 之後,就丟入給 publish function。輕鬆寫意,不過在測試的時候記得去刪掉你發表出去的測試文章!

createPost( articleId => {
  publishPost(articleId, postContent,  () => {
    console.log('完成發文');
  })
})

完整程式碼

const request = require('request');
const cheerio = require('cheerio');

// 請自行使用自己的 cookie,小心不要放在公開的地方…
var cookie = 'xxxxxx';

var postContent = {
    subject: '預先準備好的標題',
    description: '預先準備好的內容',
    tags: ['test1', 'test2'],
};

createPost(articleId => {
  publishPost(articleId, postContent,  () => {
    console.log('完成發文');
  })
})

function createPost(callback) {
  var options = {
    url: 'https://ithelp.ithome.com.tw/articles/create?group=tech',
    followRedirect: false,
    headers: {
      'cookie': cookie
    }
  };

  request(options, (err, res, body) => {
    var articleId = body.match(/articles\/(.+)\/draft/)[1];
    callback(articleId);
  })
}

function publishPost(articleId, postContent, callback) {
  var options = {
    url: `https://ithelp.ithome.com.tw/articles/${articleId}/publish`,
    method: 'POST',
    headers: {
      'Cookie': cookie,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    form: {
      // 請自行觀察 token 內容
      '_token': 'xxxxxx',
      'group': 'tech',
      '_method': 'PUT',
      'subject': postContent.subject,
      'description': postContent.description,
      'tags': postContent.tags,
    },
  };

  request(options, (err, res, body) => {
    callback()
  })
}

衍伸應用

當我們可以輕鬆的執行發文之後,我們還需要一個排程工具來幫助我們在固定時間發文。想到排程就肯定是 crontab 當能不讓了,可以將預先寫好的文章內容,用檔名分好檔案,然後排程讓 crontab 在當天就讀取該檔案的內容發送文章,如此一來,我們就只要專注在產生文章的檔案就行了。

ps. cookie 有有效期限,並且隨時可能會被 disable,所以儘可能的話,在發表文章之後,可以送一個 notifiy 到我們的 email,這樣才能萬無一失。


上一篇
番外篇 #1 - 養成持續分享的習慣
下一篇
台彩的銷售地點
系列文
爬蟲始終來自於墮性34
2
allenjung
iT邦新手 5 級 ‧ 2017-12-10 00:10:03

神一般的文章,快拜Orz

Howard iT邦新手 5 級‧ 2017-12-10 00:39:43 檢舉

教學相長,大家一起熱血分享

0
King Tzeng
iT邦新手 5 級 ‧ 2017-12-10 00:54:12

/images/emoticon/emoticon32.gif

看更多先前的回應...收起先前的回應...
Howard iT邦新手 5 級‧ 2017-12-10 02:27:22 檢舉

啊...是 Amos 的徒弟呀,昨天才聽 Amos 提到你

前輩怎知!!我只知道前輩是好想工作室的人!!
(但我當初是因為想學爬蟲才追蹤的XDDDD

Howard iT邦新手 5 級‧ 2017-12-10 16:41:39 檢舉

叫前輩太老了...
我有去看你去年參賽文,最後一篇有提到 Amos,而昨天跟 Amos 吃薑補鴨他也有提到,才聯想起來的

哈哈~昨天和師傅去吃薑母鴨XD 師傅有說我什麼嗎/images/emoticon/emoticon31.gif
好久不見師傅了~
既然是師傅的朋友,就叫你師友大大好了XDD

0
牛哥
iT邦好手 1 級 ‧ 2017-12-11 16:19:03

趕快筆記下來~
/images/emoticon/emoticon13.gif

0
Claire Chang
iT邦新手 5 級 ‧ 2017-12-15 14:17:23

太實用啦~

謝謝大大的教學,最近我把我的30篇文章都寫完了,就開始來搞這個自動發文...XD

和大家分享我寫的node js自動發文的程式
cron download

在撰寫的過程中,我覺得最困難的就是當有問題時,伺服器總是回答500 error,我很難理解這error到底是error在那邊。
是認證不過,還是.....?

最後我發現我卡住的是在tags...
因為是多選select,名稱應為tags[]而非tags

不過因為難以debug,搞好久~(淚

版大能分享當遇到500 ERROR該從那邊找線索嗎?

另外,你是怎麼知道發文的Content-Typeapplication/x-www-form-urlencoded的呢?
當時卡在500 error時,這也是我懷疑方向之一。

我發了5篇廢文觀察,還是沒辦法從攔截到我發出的廢文的格式。
(因為我用charles攔不到https,用postman模擬時和寫CODE一樣都是500 error,用瀏覽器被自動跳頁資料就不見了)

大大有推薦的攔截的工具嗎?還是我不會使用QQ

Howard iT邦新手 5 級‧ 2018-01-05 18:12:40 檢舉

基本上,chrome dev tool 就是最好用的工具。你可以從 dev tool 看到成功發送出去的 request 長怎樣,然後先求有再求好,先確定模擬一樣的 request 是可以 work,然後再一步一步刪去多餘的參數

2
pjchender
iT邦新手 5 級 ‧ 2017-12-22 17:45:42

謝謝 Howard 手把手的說明!!

在用 Postman 打 https://ithelp.ithome.com.tw/articles/create?group=tech 的時候,一直拿不到 redirect URL ,後來發現要關閉自動轉址的功能,才會看到 redirect 的 URL ,分享一下:
Imgur

Howard iT邦新手 5 級‧ 2017-12-22 17:57:05 檢舉

感謝補充

請問如何用request模擬直接登入iThome會員https://member.ithome.com.tw/login
(您上面的例子是先手動登入然後複製cookie來用)

Howard iT邦新手 5 級‧ 2017-12-26 18:02:03 檢舉

好的,我凌晨來發一篇關於登入的

0
Wen Chien
iT邦新手 5 級 ‧ 2018-10-24 16:42:08

有工程師真好xD

我要留言

立即登入留言