微信公號開發(fā)實戰(zhàn)之歷史上的今天
本篇文章主要講解如何在微信公眾帳號上實現(xiàn)“歷史上的今天”功能。這個例子本身并不復雜,但希望通過對它的學習,讀者能夠?qū)φ齽t表達式有一個新的認識,能夠?qū)W會運用現(xiàn)有的網(wǎng)絡資源豐富自己的公眾賬號。
何謂歷史上的今天
回顧歷史的長河,歷史是生活的一面鏡子;以史為鑒,可以知興衰;歷史上的每一天,都是喜憂參半;可以了解歷史的這一天發(fā)生的事件,借古可以鑒今,歷史是不能忘記的。查看歷史上每天發(fā)生的重大事情,增長知識,開拓眼界,提高人文素養(yǎng)。
尋找接口(數(shù)據(jù)源)
要實現(xiàn)查詢“歷史上的今天”,首先我們要找到相關(guān)數(shù)據(jù)源。筆者經(jīng)過搜索發(fā)現(xiàn),網(wǎng)絡上幾乎沒有現(xiàn)成的“歷史上的今天”API可以使用,所以我們只能通過爬取、解析網(wǎng)頁源代碼的方式得到我們需要的數(shù)據(jù)。筆者發(fā)現(xiàn)網(wǎng)站http://www.rijiben.com/上包含“歷史上的今天”功能,就用它做數(shù)據(jù)源了。
開發(fā)步驟
為了便于讀者理解,我們需要清楚該應用實例的開發(fā)步驟,主要如下:
1)發(fā)起HTTP GET請求,獲取網(wǎng)頁源代碼。
2)運用正則表達式從網(wǎng)頁源代碼中抽取我們需要的數(shù)據(jù)。
3)對抽取得到的數(shù)據(jù)進行加工(使內(nèi)容呈現(xiàn)更加美觀)。
4)將以上三步進行封裝,供外部調(diào)用。
5)在公眾賬號后臺調(diào)用封裝好的“歷史上的今天”查詢方法。
代碼實現(xiàn)
筆者將上述步驟1)、2)、3)中的代碼實現(xiàn)封裝成了TodayInHistoryService類,并對外提供了getTodayInHistory()方法來獲取“歷史上的今天”。實現(xiàn)代碼如下:
- import java.io.BufferedReader;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * 歷史上的今天查詢服務
- *
- * @author liufeng
- * @date 2013-10-16
- *
- */
- public class TodayInHistoryService {
- /**
- * 發(fā)起http get請求獲取網(wǎng)頁源代碼
- *
- * @param requestUrl
- * @return
- */
- private static String httpRequest(String requestUrl) {
- StringBuffer buffer = null;
- try {
- // 建立連接
- URL url = new URL(requestUrl);
- HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
- httpUrlConn.setDoInput(true);
- httpUrlConn.setRequestMethod("GET");
- // 獲取輸入流
- InputStream inputStream = httpUrlConn.getInputStream();
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- // 讀取返回結(jié)果
- buffer = new StringBuffer();
- String str = null;
- while ((str = bufferedReader.readLine()) != null) {
- buffer.append(str);
- }
- // 釋放資源
- bufferedReader.close();
- inputStreamReader.close();
- inputStream.close();
- httpUrlConn.disconnect();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return buffer.toString();
- }
- /**
- * 從html中抽取出歷史上的今天信息
- *
- * @param html
- * @return
- */
- private static String extract(String html) {
- StringBuffer buffer = null;
- // 日期標簽:區(qū)分是昨天還是今天
- String dateTag = getMonthDay(0);
- Pattern p = Pattern.compile("(.*)(<div class=\"listren\">)(.*?)(</div>)(.*)");
- Matcher m = p.matcher(html);
- if (m.matches()) {
- buffer = new StringBuffer();
- if (m.group(3).contains(getMonthDay(-1)))
- dateTag = getMonthDay(-1);
- // 拼裝標題
- buffer.append("≡≡ ").append("歷史上的").append(dateTag).append(" ≡≡").append("\n\n");
- // 抽取需要的數(shù)據(jù)
- for (String info : m.group(3).split(" ")) {
- info = info.replace(dateTag, "").replace("(圖)", "").replaceAll("</?[^>]+>", "").trim();
- // 在每行末尾追加2個換行符
- if (!"".equals(info)) {
- buffer.append(info).append("\n\n");
- }
- }
- }
- // 將buffer最后兩個換行符移除并返回
- return (null == buffer) ? null : buffer.substring(0, buffer.lastIndexOf("\n\n"));
- }
- /**
- * 獲取前/后n天日期(M月d日)
- *
- * @return
- */
- private static String getMonthDay(int diff) {
- DateFormat df = new SimpleDateFormat("M月d日");
- Calendar c = Calendar.getInstance();
- c.add(Calendar.DAY_OF_YEAR, diff);
- return df.format(c.getTime());
- }
- /**
- * 封裝歷史上的今天查詢方法,供外部調(diào)用
- *
- * @return
- */
- public static String getTodayInHistoryInfo() {
- // 獲取網(wǎng)頁源代碼
- String html = httpRequest("http://www.rijiben.com/");
- // 從網(wǎng)頁中抽取信息
- String result = extract(html);
- return result;
- }
- /**
- * 通過main在本地測試
- *
- * @param args
- */
- public static void main(String[] args) {
- String info = getTodayInHistoryInfo();
- System.out.println(info);
- }
- }
代碼解讀:
1)27-58行代碼是httpRequest()方法,用于發(fā)起http get請求,獲取指定url的網(wǎng)頁源代碼。
2)66-92行代碼是extract()方法,運用正則表達式從網(wǎng)頁源代碼中抽取“歷史上的今天”數(shù)據(jù)。
3)111-118行代碼是getTodayInHistory()方法,封裝給外部調(diào)用查詢“歷史上的今天”。
4)125-128行代碼是main方法,用于在本地的開發(fā)工具中測試。
5)75-76行代碼的作用是判斷獲取到的“歷史上的今天”數(shù)據(jù)是當天的還是前一天的(因為不能保證www.rijiben.com上的數(shù)據(jù)一定在凌晨零點準時更新,所以為了保證數(shù)據(jù)的準確性必須做此判斷)。
6)第71行代碼是本文的重點,筆者編寫的正則表達式規(guī)則是“(.*)(<div class=\"listren\">)(.*?)(</div>)(.*)”。正則表達式規(guī)則需要根據(jù)網(wǎng)頁源代碼進行編寫的,特別是包含“歷史上的今天”數(shù)據(jù)的那部分HTML標簽,所以我們先來查看網(wǎng)頁源代碼。通過httpRequest("http://www.rijiben.com/")方法獲取到的網(wǎng)頁源代碼,與我們通過瀏覽器訪問http://www.rijiben.com/頁面再點擊右鍵選擇“查看網(wǎng)頁源代碼”所得到的結(jié)果完全一致。我們通過瀏覽器查看http://www.rijiben.com/的網(wǎng)頁源代碼,然后找到“歷史上的今天”數(shù)據(jù)所在位置,如下圖所示:
從上面的源代碼截圖中可以看到,我們需要的數(shù)據(jù)被包含在<div class="listren">標簽內(nèi),這樣就不難理解為什么正則表達式要這樣寫:
(.*)(<div class=\"listren\">)(.*?)(</div>)(.*)
我們使用括號()將正則表達式規(guī)則分成了5組,下面是這些分組的說明:
- 第1組:(.*)表示網(wǎng)頁源代碼中<div class="listren">標簽之前還有任意多個字符。
- 第2組:(<div class=\"listren\">)中的反斜杠表示轉(zhuǎn)義,所以該規(guī)則就是用于匹配<div class="listren">。
- 第3組:(.*?)表示在標簽<div class="listren">和</div>之間的所有內(nèi)容,這才是我們真正需要的數(shù)據(jù)所在。
- 第4組:(</div>)就是用于匹配<div class="listren">的結(jié)束標簽。
- 第5組:(.*)表示在</div>標簽之后還有任意多的字符。
掌握了正則表達式規(guī)則的含義,就不難理解為什么在extract()方法中全都是在使用m.group(3),因為m.group(3)就表示匹配到數(shù)據(jù)的第3個分組。m.group(3)的內(nèi)容如下:
- <ul> <li><a href="/news6836/" title="0690年10月16日 武則天登上皇位">0690年10月16日 武則天登上皇位</a> (圖)</li> <li><a href="/news6837/" title="1854年10月16日 唯美主義運動的倡導者王爾德誕辰">1854年10月16日 唯美主義運動的倡導者王爾德誕辰</a> </li> <li><a href="/news6838/" title="1854年10月16日 德國社會主義活動家考茨基誕生">1854年10月16日 德國社會主義活動家考茨基誕生</a> </li> <li><a href="/news6839/" title="1908年10月16日 阿爾巴尼亞領(lǐng)導人恩維爾·霍查誕辰">1908年10月16日 阿爾巴尼亞領(lǐng)導人恩維爾·霍查誕辰</a> (圖)</li> <li><a href="/news6840/" title="1913年10月16日 中國“兩彈一星”元勛錢三強誕辰">1913年10月16日 中國“兩彈一星”元勛錢三強誕辰</a> (圖)</li> <li><a href="/news6841/" title="1922年10月16日 開灤煤礦工人罷工失敗">1922年10月16日 開灤煤礦工人罷工失敗</a> (圖)</li> <li><a href="/news6842/" title="1927年10月16日 德國諾貝爾文學獎得主格拉斯誕生">1927年10月16日 德國諾貝爾文學獎得主格拉斯誕生</a> (圖)</li> <li><a href="/news6843/" title="1933年10月16日 抗日同盟軍失敗">1933年10月16日 抗日同盟軍失敗</a> (圖)</li> <li><a href="/news6844/" title="1950年10月16日 人民解放軍進軍西藏">1950年10月16日 人民解放軍進軍西藏</a> (圖)</li> <li><a href="/news6845/" title="1954年10月16日 俞平伯《關(guān)于紅樓夢研究問題的信》發(fā)表">1954年10月16日 俞平伯《關(guān)于紅樓夢研究問題的信》發(fā)表</a> (圖)</li> <li><a href="/news6846/" title="1959年10月16日 美軍將領(lǐng)、國務卿馬歇爾去世">1959年10月16日 美軍將領(lǐng)、國務卿馬歇爾去世</a> (圖)</li> <li><a href="/news6847/" title="1964年10月16日 勃列日涅夫取代赫魯曉夫 成為蘇共中央第一書記">1964年10月16日 勃列日涅夫取代赫魯曉夫 成為蘇共中央第一書記</a> </li> <li><a href="/news6848/" title="1964年10月16日 我國第一顆原子彈爆炸成功">1964年10月16日 我國第一顆原子彈爆炸成功</a> (圖)</li> <li><a href="/news6849/" title="1973年10月16日 震撼世界的石油危機爆發(fā)">1973年10月16日 震撼世界的石油危機爆發(fā)</a> (圖)</li> <li><a href="/news6850/" title="1978年10月16日 約翰·保羅二世當選新教皇">1978年10月16日 約翰·保羅二世當選新教皇</a> </li> <li><a href="/news6851/" title="1979年10月16日 哈克將軍宣布巴基斯坦推遲大選解散政黨">1979年10月16日 哈克將軍宣布巴基斯坦推遲大選解散政黨</a> </li> <li><a href="/news6852/" title="1984年10月16日 圖圖主教榮獲“諾貝爾和平獎”">1984年10月16日 圖圖主教榮獲“諾貝爾和平獎”</a> </li> <li><a href="/news6853/" title="1988年10月16日 北京正負電子對撞機對撞成功">1988年10月16日 北京正負電子對撞機對撞成功</a> (圖)</li> <li><a href="/news6854/" title="1991年10月16日 美國小鎮(zhèn)槍殺案22人喪生">1991年10月16日 美國小鎮(zhèn)槍殺案22人喪生</a> </li> <li><a href="/news6855/" title="1991年10月16日 莫扎特死因有新說">1991年10月16日 莫扎特死因有新說</a> </li> <li><a href="/news6856/" title="1991年10月16日 錢學森獲“國家杰出貢獻科學家”殊榮">1991年10月16日 錢學森獲“國家杰出貢獻科學家”殊榮</a> (圖)</li> <li><a href="/news6857/" title="1994年10月16日 德國總理科爾四連任">1994年10月16日 德國總理科爾四連任</a> </li> <li><a href="/news6858/" title="1994年10月16日 第十二屆廣島亞運會閉幕">1994年10月16日 第十二屆廣島亞運會閉幕</a> </li> <li><a href="/news6859/" title="1994年10月16日 修秦陵制秦俑工匠墓葬被發(fā)現(xiàn)">1994年10月16日 修秦陵制秦俑工匠墓葬被發(fā)現(xiàn)</a> </li> <li><a href="/news6860/" title="1995年10月16日 美國百萬黑人男子大游行">1995年10月16日 美國百萬黑人男子大游行</a> (圖)</li> </ul>
可以看到,通過正則表達式抽取得到的m.group(3)中仍然有大量的html標簽、空格、換行、無關(guān)字符等。我們要想辦法把它們?nèi)窟^濾掉,第83行代碼的作用正是如此。
組裝文本消息
- // 組裝文本消息(歷史上的今天)
- TextMessage textMessage = new TextMessage();
- textMessage.setToUserName(fromUserName);
- textMessage.setFromUserName(toUserName);
- textMessage.setCreateTime(new Date().getTime());
- textMessage.setMsgType(WeixinUtil.RESP_MESSAGE_TYPE_TEXT);
- textMessage.setFuncFlag(0);
- textMessage.setContent(TodayInHistoryService.getTodayInHistoryInfo());
效果示例:
說明:與其說這是一篇關(guān)于公眾帳號應用開發(fā)的教程,倒不如說這是一篇關(guān)于網(wǎng)頁數(shù)據(jù)爬取的教程。本文旨在為讀者開辟思路,介紹一種數(shù)據(jù)獲取方式。當然,這種做法也是有弊端的,當網(wǎng)頁改版源代碼結(jié)構(gòu)發(fā)生變化時,就需要重新改寫數(shù)據(jù)抽取代碼。