《HelloGitHub》之GitHub Bot


起因

我在github上發起了一個開源項目:《HelloGitHub月刊》,內容是github上收集的好玩,容易上手的開源項目。

目的:因為興趣是最好的老師,我希望月刊中的內容可以激發讀者的興趣,從而動手參與到開源的項目中,一方面提高編程技術、另一方面哪怕是能力有限不能為開源項目提交代碼,也可以給個‘star’,表示對有意思、優秀的開源項目的支持!從而讓開源社區越來越好。

所以,我就需要收集github上的開源項目,目前通過兩種方式發現github上優秀的項目:

  1. Follow活躍的Github用戶,收集他們Starred的項目
  2. Github的Explore頁

然后,我就想能不能寫個腳本,每天跑一次把這兩個數據源的數據,收集整理好,然后發到我的郵箱中。這個需求很簡單,初步感覺就兩個問題:

  1. 數據源的api
  2. 發郵件的方法

過程

數據源

Github API提供了諸多獲取Github數據的接口:

  1. List public events that a user has receivedGET /users/:username/received_events/public,這個接口返回user的動態(包含user關注的用戶、項目的動態)
  2. 暫時沒找到Explore頁的接口,如果實在找不到,我就嘗試爬取。

代碼

完整的代碼我的Github

1、請求api:

首選requests庫,真的是居家旅行必備良品。需要注意一點,請求GET /users/:username/received_events/public需要用戶驗證,請求api的函數如下:

def get_data(page=1, per_page=100):
    """
    從目標源獲取數據
    """

    args = '?page={page}&per_page={per_page}'.format(
        page=page, per_page=per_page)

    response = requests.get(API['events']+args,
                            auth=(ACCOUNT['username'], ACCOUNT['password']))
    status_code = response.status_code
    if status_code == 200:
        resp_json = response.json()
        return resp_json
    else:
        logging.error('請求api失敗:', status_code)
        return None
2、根據條件過濾數據:

請求api回來的json數據如下:

[
	...
  {
    "id": "4123123423",
    "type": "WatchEvent",
    "actor": {
      "id": 12342134,
      "login": "gera2ld",
      "display_login": "gera2ld",
      "gravatar_id": "",
      "url": "https://api.github.com/users/gera2ld",
      "avatar_url": "https://avatars.githubusercontent.com/u/3139113?"
    },
    "repo": {
      "id": 23412431,
      "name": "yahoo/gifshot",
      "url": "https://api.github.com/repos/yahoo/gifshot"
    },
    "payload": {
      "action": "started"
    },
    "public": true,
    "created_at": "2016-09-03T16:25:34Z",
    "org": {
      "id": 234234,
      "login": "yahoo",
      "gravatar_id": "",
      "url": "https://api.github.com/orgs/yahoo",
      "avatar_url": "https://avatars.githubusercontent.com/u/16574?"
    }
  },
  {
    "id": "234234",
    "type": "WatchEvent",
    "actor": {
      "id": 21341234,
      "login": "phith0n",
      "display_login": "phith0n",
      "gravatar_id": "",
      "url": "https://api.github.com/users/phith0n",
      "avatar_url": "https://avatars.githubusercontent.com/u/5711185?"
    },
    "repo": {
      "id": 23234,
      "name": "yummybian/ThreadPool",
      "url": "https://api.github.com/repos/yummybian/ThreadPool"
    },
    "payload": {
      "action": "started"
    },
    "public": true,
    "created_at": "2016-09-03T16:12:56Z"
  }
]
	...

分析上面的json數據,其中可能會包含我不需要的信息(非starred事件的數據)需要過濾掉、同時需要根據時間獲取某一段時間的數據。比如我寫的這個github bot腳本獲取24個小時的數據,我設定腳本每天凌晨4點跑——例如:9.3 4:00——9.4 4:00(24h的數據)。下面寫了兩個函數,用於過濾符合條件的數據:

注意: 接口返回的數據中的create_at字段的時間值形如——created_at: "2016-09-03T16:12:56Z" 是‘協調世界時’,‘Z’是協調世界時中0時區的標志,北京是8時區,所以就是需要在這個時間的基礎上+8小時。這個事件發生於北京時間:"2016-09-04 00:12:56"

def check_condition(data):
    """
    過濾條件
    """
    create_time = datetime.datetime.strptime(
        data['created_at'], "%Y-%m-%dT%H:%M:%SZ") + datetime.timedelta(hours=8)
    date_condition = create_time >= (datetime.datetime.now()
                                     - datetime.timedelta(days=DAY))
    if (data['type'] == 'WatchEvent') and date_condition:
        if data['payload']['action'] == 'started':
            data['date_time'] = create_time.strftime("%Y-%m-%d %H:%M:%S")
            return True
    else:
        return False

def analyze(json_data):
    """
    分析獲取的數據
    :return 符合過濾條件的數據
    """
    result_data = []
    for fi_data in json_data:
        if check_condition(fi_data):
            result_data.append(fi_data)
    return result_data
3、生成發送郵件的內容:

最終郵件內容如下:

注意: 因為獲取項目stars數的接口,有的時候獲取數據很慢,所以設置了超時時間。最好的方法因該是以異步的方式解決。可以參考grequests項目

CONTENT_FORMAT = """
    <table border="2" align="center">
      <tr>
        <th>頭像</th>
        <th>用戶名</th>
        <th>項目名</th>
        <th>starred日期</th>
        <th>項目star數量</th>
      </tr>
      {starred_info}
    </table>
"""

def make_content():
    """
    生成發布郵件的內容
    """
    json_data = get_data()
    data = analyze(json_data)
    content = []

    for fi_data in data:
        user = fi_data['actor']['login']
        user_url = 'https://github.com/' + user
        avatar_url = fi_data['actor']['avatar_url']
        repo_name = fi_data['repo']['name']
        repo_url = 'https://github.com/' + repo_name
        date_time = fi_data['date_time']
        try:
            repo_stars = requests.get(fi_data['repo']['url'], timeout=2).json()
            if repo_stars:
                repo_stars = repo_stars['stargazers_count']
            else:
                repo_stars = '未知數'
        except Exception as e:
            repo_stars = '未知數'
            logger.warning(u'獲取:{} 項目星數失敗——{}'.format(repo_name, e))
        starred_info = """<tr>
                            <td><img src={avatar_url} width=32px></img></td>
                            <td><a href={user_url}>{user}</a></td>
                            <td><a href={repo_url}>{repo_name}</a></td>
                            <td>{date_time}</td>
                            <td>{repo_stars}</td>
                          </tr>
                       """.format(user=user, repo_name=repo_name,
                                  repo_url=repo_url, user_url=user_url,
                                  avatar_url=avatar_url, repo_stars=repo_stars,
                                  date_time=date_time)
        content.append(starred_info)
    return content
4、發送郵件:

如果是使用qq郵箱發送郵件,可以參考:qq郵件服務文檔

注意: 發送郵件使用的郵箱密碼,最好用授權碼,因為我在測試郵件發送功能時,發送郵件的次數太多,后面突然不能發送了!而且沒有任何錯誤提示,就卡在sendmail方法!后來登錄qq郵箱,發現讓我使用授權碼 進行第三方授權。最后使用授權碼一切就ok了!

發送郵件的函數如下:

def send_email(receivers, email_content):
    """
    發送郵件
    """
    sender = MAIL['mail']  # 發送郵件的郵箱
    receivers = receivers   # 接收郵件的郵箱,可設置多個

    # 三個參數:第一個為文本內容,第二個 html 設置文本格式,第三個 utf-8 設置編碼
    message = MIMEText(
        CONTENT_FORMAT.format(starred_info=''.join(email_content)),
        'html', 'utf-8'
    )
    message['From'] = Header(u'Github機器人', 'utf-8')
    message['To'] = Header(u'削微寒', 'utf-8')

    subject = u'今日Github熱點'  # 設置郵件主題
    message['Subject'] = Header(subject, 'utf-8')
    try:
        smtp_obj = smtplib.SMTP_SSL()  # qq郵箱要求是https連接,所以需要用SMTP_SSL
        smtp_obj.connect(MAIL['host'], MAIL['port'])    # 設置SMTP地址和端口號
        smtp_obj.login(MAIL['username'], MAIL['password'])
        smtp_obj.sendmail(sender, receivers, message.as_string())
    except smtplib.SMTPException as e:
        logger.error(u"無法發送郵件: {}".format(e))

完整的代碼我的Github

最后

參照 crontab 定時任務,在linux下設置定時任務。

1. EDITOR=vi; export EDITOR #使用vi編輯器編輯

2. crontab -e #加入定時任務

3. crontab -l #查看是否加入成功

TODO

  1. 獲取Explore頁的數據

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM