2018年8月25日 星期六

News API新聞擷取的好幫手 - 使用Python

  工作上被指派為某些特定的專案(如巨量資料、雲端運算等)收集相關新聞,並更新至公司的網頁系統平台上,然而查找新聞的任務是一件極其耗時費力又無太大意義的工作,因此決定使用Python網站擷取的技術,讓機器人來幫我們收集、整理資料成Excel檔,屆時只要按個按鈕,將Excel檔批次上傳即可;然而當我們實際爬取網頁資料時,才發現各式資料被套疊的格式不一致、資料被層層保護與變造,以及JavaScript與網頁跳轉等的問題,幾乎要逼迫我們放棄使用程式技術來幫我們完成工作,接受土法煉鋼地以人工方式一筆一筆查找新聞資料。

  往往在遇到困難的時候,換個思考方向即可找到更好的解決方案,因此不要埋頭苦幹完成任務,畢竟自己沒有好的編程能力,沒有必要硬是使用目前僅有的技術而顯得黔驢技窮。

  當網站擷取變得越來越困難,專業化的應用服務也會越來越興盛,尤其是新聞擷取這種常見的資訊需求,一定也有API等整理好的資源可以使用,因此我找到了News API,可以完美地幫我們完成收集新聞的任務。News API本身也是透過網站擷取來收集新聞,目前已收集超過30000個出處、54個國家,我們可以查看新聞出處,了解News API已將所有新聞爬得又深又廣,亦不會胡亂擷取內容真實性有疑慮的八卦小報新聞。

News API也有擷取中華民國(台灣)的新聞。

  以下內容分為「關於News API」、「儲存至news.csv」、「儲存至news.xlsx」、「修改news.xlsx並儲存至selectedNews.xlsx」共四個部分,其中修改news.xlsx的部分是依照公司系統欄位需求所做的調整,試想當其他同事還在辛苦地一筆一筆查找新聞資料時,我們已經又快又好地完成任務,是多麼有成就感呀!

  收集並更新新聞的工作如果每天都必須執行,我們甚至可以寫支程式,讓資料直接存入資料庫,透過每日一次的排程讓電腦自動執行,往後公司網頁系統平台上顯示的主題新聞,都不再需要透過人工作業來維護,讓人力可以專注在其它重要的任務上,這聽起來不是很棒嗎?

一、關於News API

News API基本介紹
l   Homepage: News API
l   Subscription Plan: Developer
Free for Open Source Project, Non-commercial Project, and Commercial Project in Development
開發者權限完全免費,但是有使用限制,以避免資源被隨意耗用:
6個小時可以向News API請求250次,每24個小時可以向News API請求1000次。
News API每次預設回傳20則新聞,最多每次指定回傳100則新聞,換句話說,每6個小時最多可以擷取25000則新聞,以關鍵字搜尋新聞而言,約為累積10個月以上的新聞則數,以免費的權限來說,已經非常好用了。
l   Documentation:
News API
回傳JSON格式的資料,請求資料時可以傳送的變數,以及收到資料的結構,網站文件皆有清楚說明。
News API
目前提供頭條搜尋(Top Headlines)、關鍵字搜尋(Everything),以及類別搜尋(Sources)3種搜尋方法。
l   Register for API Key:
註冊並取得API Key之後,便可以傳送網址,收到JSON資料,例如:
https://newsapi.org/v2/everything?q="
巨量資料"OR"大數據"&from=2018-06-21&to=2018-06-21&language=zh&page=1&apiKey=API_KEY

News API Python Client Library
l   News API可以透過多種程式語言進行操作,亦提供各種程式語言套件,然而以Python操作來說,使用套件並沒有比較方便,反而還可能要修改Library的語法,例如中文代碼'zh'沒有被加入套件的const.py檔案,必須自己補上,詳細資訊可參考News API Python Client LibraryGitHub
l   在命令提示字元輸入pip install newsapi-python,下載並安裝News API Python Client Library
l   from newsapi import NewsApiClient
API_KEY = input('Your API key is: ')
newsapi = NewsApiClient(api_key=API_KEY)
all_articles = newsapi.get_everything(q='"
巨量資料"OR"大數據"', from_param='2018-06-21', to='2018-06-21', language='zh', page=1)
print(all_articles)

二、儲存至news.csv

import requests, json, csv

API_KEY = input('Your API key is: ')
pageNum = 1
totalPageNum = 1

newsFile = open('D:\\news.csv', 'w', newline = '', encoding = 'utf-8-sig')
newsWriter = csv.writer(newsFile)
newsWriter.writerow(['id', 'name', 'author', 'title', 'description', 'url', 'urlToImage', 'publishedAt'])
newsFile.close()

def newsAPIPages():
    newsFile = open('D:\\news.csv', 'a', newline = '', encoding = 'utf-8-sig')
    newsWriter = csv.writer(newsFile)
   
    url = ('https://newsapi.org/v2/everything?'
        'q="巨量資料"OR"大數據"&'
        'from=2018-06-21&'
        'to=2018-06-21&'
        'language=zh&'
        'page=' + str(pageNum) + '&'
        'apiKey=' + API_KEY)
    aRes = requests.get(url)
    newsDict = json.loads(aRes.text)
   
    print('status: ' + newsDict['status'])
    print('totalResults: ' + str(newsDict['totalResults']))
    global totalPageNum
    totalPageNum = int(newsDict['totalResults']/20) + 1
    print('Retrieving Page ' + str(pageNum) + '/' + str(totalPageNum))
   
    for article in newsDict['articles']:
        newsWriter.writerow([article['source']['id'], \
            article['source']['name'], \
            article['author'], \
            article['title'], \
            article['description'], \
            article['url'], \
            article['urlToImage'], \
            article['publishedAt']])
   
    newsFile.close()

newsAPIPages()
while pageNum < totalPageNum:
    pageNum += 1
    newsAPIPages()

三、儲存至news.xlsx

import requests, json, openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string

API_KEY = input('Your API key is: ')
pageNum = 1
totalPageNum = 1
columnNum = 1
rowNum = 2

newsBook = openpyxl.Workbook()
newsSheet = newsBook.active
newsSheet.title = 'news'
header = ['id', 'name', 'author', 'title', 'description', 'url', 'urlToImage', 'publishedAt']
for item in header:
    newsSheet[get_column_letter(columnNum) + '1'] = item
    columnNum += 1
newsBook.save('D:\\news.xlsx')

def newsAPIPages():
    newsBook = openpyxl.load_workbook('D:\\news.xlsx')
    newsSheet = newsBook.active
   
    url = ('https://newsapi.org/v2/everything?'
        'q="巨量資料"OR"大數據"&'
        'from=2018-06-21&'
        'to=2018-06-21&'
        'language=zh&'
        'page=' + str(pageNum) + '&'
        'apiKey=' + API_KEY)
    aRes = requests.get(url)
    newsDict = json.loads(aRes.text)
   
    print('status: ' + newsDict['status'])
    print('totalResults: ' + str(newsDict['totalResults']))
    global totalPageNum
    totalPageNum = int(newsDict['totalResults']/20) + 1
    print('Retrieving Page ' + str(pageNum) + '/' + str(totalPageNum))
   
    global columnNum
    global rowNum
    for article in newsDict['articles']:
        articleList = []
        articleList.append(article['source']['id'])
        articleList.append(article['source']['name'])
        articleList.append(article['author'])
        articleList.append(article['title'])
        articleList.append(article['description'])
        articleList.append(article['url'])
        articleList.append(article['urlToImage'])
        articleList.append(article['publishedAt'])
        columnNum = 1
        for item in articleList:
            newsSheet[get_column_letter(columnNum) + str(rowNum)] = item
            columnNum += 1
        rowNum += 1
   
    newsBook.save('D:\\news.xlsx')

newsAPIPages()
while pageNum < totalPageNum:
    pageNum += 1
    newsAPIPages()

四、修改news.xlsx並儲存至selectedNews.xlsx

# 修改目標一:挑選並排序欄位
# 修改目標二:只挑出連結名稱(title)有關鍵字的新聞
# 修改目標三:排除相同連結名稱(title)的新聞
# 修改目標四:變更日期(publishedAt)格式

import openpyxl, re
from openpyxl.utils import get_column_letter, column_index_from_string

newsBook = openpyxl.load_workbook('D:\\news.xlsx')
newsSheet = newsBook.active

selectedNewsBook = openpyxl.Workbook()
selectedNewsSheet = selectedNewsBook.active
selectedNewsSheet.title = 'selectedNews'

header = ['連結名稱(title)', '出處(name)', '日期(publishedAt)', '連結網址(url)']
selectedColumnNum = 1
for item in header:
    selectedNewsSheet[get_column_letter(selectedColumnNum) + '1'] = item
    selectedColumnNum += 1

titleList = []
selectedRowNum = 2
for row in range(2, newsSheet.max_row + 1):
    bufferedList = []
   
    # title
    originalTitle = str(newsSheet['D' + str(row)].value)
    aRegex = re.compile(r'巨量資料|大數據')
    if aRegex.search(originalTitle) == None:
        continue
    if originalTitle in titleList:
        continue
    titleList.append(originalTitle)
    bufferedList.append(originalTitle)
   
    # name
    bufferedList.append(newsSheet['B' + str(row)].value)
   
    # publishedAt
    originalFormat = newsSheet['H' + str(row)].value
    bRegex = re.compile(r'\d\d\d\d-\d\d-\d\d')
    bufferedFormat = str(bRegex.search(originalFormat).group())
    newFormat = bufferedFormat.replace('-', '/')
    bufferedList.append(newFormat)
   
    # url
    bufferedList.append(newsSheet['F' + str(row)].value)
   
    selectedColumnNum = 1
    for item in bufferedList:
        selectedNewsSheet[get_column_letter(selectedColumnNum) + str(selectedRowNum)] = item
        selectedColumnNum += 1
    selectedRowNum += 1

selectedNewsBook.save('D:\\selectedNews.xlsx')
newsBook.close()

3 則留言:

  1. 作者已經移除這則留言。

    回覆刪除
  2. 請教一下,您在文章一開始提到「工作上被指派…」,但這似乎和 News API 提供的 "免費" 條件 (禁止商用) 衝突 (https://newsapi.org/pricing)。請教您是否有別的法務解釋或是處理辦法可以參考呢?

    回覆刪除
    回覆
    1. 很好的問題:

      1. 新聞資料本身不是需要付費與否的因素,而是使用API這項服務來達成自己的獲益目的,這才存在商用與否的疑慮。以我在前份工作使用這個API服務的目的來說,僅為取得新聞資料而已,且新聞資料整理後所放置的網頁系統平台,完全沒有商業行為(是政府單位的服務,所以一個廣告都沒有),因此沒有商用的疑慮。

      2. 對API服務提供者來說,大量的資料請求需求是耗用資源的關鍵,所以Developer免費的權限限制很多(在我寫這篇網誌的時候,免費的權限還不像現在有那麼大的限制),所以即便多數使用情境下,不容易違反"禁止商用"條款,也會因為不敷使用而得升級為Business或Enterprise付費的權限。

      3. 若真的有使用此API服務來達成自己的商業行為,只要是原型階段的商業行為,仍被接受的(For all non-commercial projects and in-development commercial projects.),如果是具規模、成熟的商業行為,還是建議付費使用(其實在此階段之前,免費的權限應該早就不夠用了)。

      刪除