一隻Python小爬蟲的Linux定時任務之旅

目錄

  1. 起因
  2. systemd守護進程
  3. 單元(Units)
    3.1. 單元加載路徑(Unit load path)
    3.2. 通用節(section)
    3.2.1. [Unit]
    3.2.2. [Install]
  4. Service配置選項
  5. Timer配置選項
  6. systemctl命令
    6.1. 與查看Unit狀態相關
    6.2. 與操作Unit相關
    6.3. 與守護進程相關
  7. systemd如何啓動服務
  8. References
  9. Materials

1. 起因

由於衆所周知的原因,谷歌搜索引擎正常情況下無法使用,某度又實在令人氣憤,因此,從16年開始,我就一直使用微軟的必應(Bing)搜索引擎。必應有個特色就是它的主界面是自帶背景圖的(如圖1),而且每天一換。
Fig 1 Bing bg image

個人感覺每天選取的圖片都還挺不錯的,清晰度也很高,因此萌生了一個將它背景圖片下載下來的想法。整好週末閒來無事,說幹就幹。

這其實就是一個再簡單不過的爬蟲,不需要任何複雜的欺騙手段,唯一需要做的就是查看瀏覽器是怎麼將背景圖下載下來的,然後模仿一下瀏覽器就行。由於一開始使用谷歌瀏覽器的開發者功能無法直接定位背景圖元素位置,還走了點小彎路:查看源碼的時候發現有一大串base64編碼的字符,我以爲這就是背景圖的編碼,用正則表達式提取後解碼發現就只是一些簡單的圖標而已。

下載背景圖片的核心代碼非常簡單(GitHub Base:https://github.com/zmychou/bing-bg-downloader
),也就二十來行,也不需要除標準庫外的任何三方庫,主要問題是如何運行。因爲背景圖片每天更新,因此我們的小爬蟲只需要每天爬取一次。有三種方案:

  1. 每天手動執行一次。主要問題是麻煩,每天還要定個鬧鐘;
  2. 程序啓動後,爬取一次後休眠,定時醒來檢查是否已經是第二天。缺點是當機器重啓後需要重新手動啓動爬蟲,並且,一直佔用着系統的資源,雖然是個小爬蟲,但是這種佔着茅坑不拉shi的做法並不可取,除非走投無路;
  3. 使用系統的定時任務功能。缺點是需要適配不同的系統,但是自己日常只用烏班圖,簡單點只關注Linux下的定時任務系統即可。

敲定了,就是Linux下的定時任務來實現我們每天爬取一張背景圖的需求。

2. systemd守護進程

Linux系統中有一個叫做systemd的守護進程(Linux中一般以d結尾的程序都是守護進程),他是系統和服務的管理者(也可以說是守護者),它可以說是Linux系統的中流砥柱,很多系統功能的實現都是以它爲基礎。例如主機名、日期、本地化等的配置文件的管理;已登陸用戶、容器、虛擬機、簡單網絡處理的守護進程、域名解析、網絡時間同步和日誌轉發等功能的維護,等等。

而定時任務是systemd提供的功能之一。想要在Linux下實現定時任務,需要三個夥計的幫忙:

  1. systemctl:他是查看systemd狀態和控制systemd行爲的主要命令;
  2. .service單元(Unit):告訴systemd做什麼;
  3. .timer單元:告訴systemd什麼時候做。

其中.service.timer稱爲單元(Unit),其他類型的還有.devicesocket.mount等,都是通過一個INI格式的配置文件來實現的,這寫配置文件分別以後綴.service.timer等結尾,都稱爲單元文件(Unit files),並且這些單元文件有固定的目錄來存放,用於實現不同的服務。

因此,開啓一個定時服務的操作可以分爲以下四步:

  1. 編寫.service單元文件;
  2. 編寫.timer單元文件;
  3. 將前兩步編寫好的單元文件放到指定目錄;
  4. 使用systemctl來使單元文件生效。

在我們的爬蟲例子中,我們爲定時任務編寫的.service.timer單元文件如下所示:

# downloader.service

[Unit] 
Description= Bing background image downloader 

[Service]
Type= simple
WorkingDirectory=/home/zhou/workspace/bing-bg-downloader
ExecStart=/usr/bin/python3 /home/zhou/workspace/bing-bg-downloader/downloader.py
# downloader.timer

[Unit] 
Description= Bing background image downloader

[Timer] 
Unit= downloader.service 
OnCalendar=*-*-* 15:00:07
Persistent=true

[Install] 
WantedBy=basic.target

然後,我們將它們放置在$HOME/.config/systemd/user/目錄下:

mkdir -p ~/.config/systemd/user
cp downloader.service ~/.config/systemd/user/downloader.service
cp downloader.timer ~/.config/systemd/user/downloader.timer

最後我們通過systemctl命令來激活我們的定時任務:

systemctl --user daemon-reload 
systemctl --user enable downloader.service
systemctl --user enable downloader.timer
systemctl --user start downloader.timer
systemctl --user list-timers --all

至此,我們爲我們的小爬蟲設置的定時器就算完成了,我們的小爬蟲就會在每天的下午3點啓動,然後對背景圖片進行爬取,爬取完成後就退出了,等待第二天定時器在次將它啓動。

但是顯然,這個例子只對我們的小爬蟲好使。我們只知其然,卻不知其所以然。例如,我們如果想讓這隻小爬蟲每個星期三爬取一次,我們要如何改呢?爲了能夠隨心所欲的定製我們的定時任務,我們有必要來了解單元文件裏都可以有哪些內容,每一條內容代表什麼意思。

3. 單元(Units)

3.1. 單元加載路徑(Unit load path)

首先,特定服務的單元文件,也就是配置文件,是要被放置在一組特定目錄下,這些特定的目錄稱爲單元加載路徑(Unit load path)。
加載路徑分兩大類:

  1. 系統模式:當使用systemctl --system的時候的搜索路徑,系統模式的搜索路徑如表1所示;
  2. 用戶模式:當使用systemctl --user的時候的搜索路徑,用戶模式的搜索路徑如表2所示。在我們的例子中,我們就是將我們對額單元文件放到了用戶模式的$HOME/.config/systemd/user加載路徑下。

值得注意的是,每一類搜索路徑在表格中的先後順序是有意義的,從上到下優先級依次降低。也就是說,如果排在前面的路徑中與排在後面的路徑中有同名文件,systemd會選擇排在前面的文件夾中的文件,忽略後面優先級比較低的目錄中的同名文件。除此以外,還可以通過指定$SYSTEMD_UNIT_PATH這個環境變量來指定一個最高優先級的搜索目錄。

Tab 1 Load path when running in system mode (--system)
Tab 2 Load path when running in user mode (--user)

3.2. 通用節(section)

如前所述,定時任務等服務是通過一個稱爲單元文件的配置文件來實現的,單元文件的格式採用類似.ini文件格式。單元文件由節(section)分成不同的部分,內個節可以包含很多選線(Option),選項由鍵值對組成。形式如下:

# Some comments here
[Section1]
key1=value1 
key2=value2

[section2]
key3=value3
key4=value4
# More sections below.

其中,有兩個節是通用的,就是不管什麼類型的單元文件都可以有的節,他們就是[Unit][Install]。我們首先來看看這兩個節都可以包含什麼選項以及他們的含義。

3.2.1. [Unit]

[Unit]節中描述的是本單元文件的通用信息。可以有的選項(Option)如下,爲了方便描述,這裏約定鍵值對的表現形式爲keyname= <key value>

  1. Description= <A human readable name for the unit>:主要用於描述本單元文件是什麼,一般只是用於給此單元文件命名而不是具體描述這個單元文件都做了什麼,因爲systemd 會使用它作爲一些log的賓語,例如Starting xxxxStarted xxxxx,因此它的值只要能描述這個文件是什麼就行。例如:
[Unit]
Description= Bing backround image downloader
  1. Documentation= <A space-separated list of URIs referencing documentation for this unit or its configuration>:由於選項Description=推薦的用法是指描述本單元文件的名稱,因此對於這個單元文件的詳細描述你可以放在這個選項中,它的值是URI地址,可以使用的URI類型爲"http://", "https://", "file:", "info:", "man:"這五種,多個URI中使用空格區分;例如:
[Unit]
Documentation= http://cn.bing.com https://github.com/zmychou/bing-bg-downloader
  1. Wants= <Configures requirement dependencies on other units>:列出對其他單元文件的依賴,如果有多個依賴可以使用多個Wants=選項或者在一個Wants=現象中使用空格分隔多個依賴。Wants=只列出了依賴,而不指定依賴執行的先後。並且如果某個依賴執行失敗不會影響本單元執行;
    Before=, After= <Configures requirement dependencies on other units>:與Wants=類似,列出本單元間的依賴,這些依賴執行的先後是有要求的,其順序是After==>本單元=>Before=
    Requires= <Configures requirement dependencies on other units>:與Wants=類似,與Wants=不同的是,如果某個依賴無法正常啓動,則本單元也不會啓動;

  2. Conflicts= <A space-separated list of unit names>:描述一種互斥關係,如果啓動本單元,就會終止列出的其他單元,反過來也成立;

  3. OnFailure= <A space-separated list of units>:描述當本單元進入failed狀態後,那些單元會被啓動;

  4. FailureAction=, SuccessAction= <Action>:Action可以是none, reboot, reboot-force, reboot-immediate, poweroff, poweroff-force, poweroff-immediate, exit, exit-force的任何一種,當使用--user模式的時候,只能是none, exit, exit-force中的一種;

3.2.2. [Install]

節[Install]包含了本單元的一些安裝信息。當使用systemctl enablesystemctl disable命令的時候就會使用到這部分信息。

  1. Alias= <A space-separated list of additional names>:給出本單元的別名,別名也和真名類似,需要以類型的後綴結尾;

  2. WantedBy=, RequiredBy= <A space-separated list of unit names>:當列出的單元被啓動的時候,本單元也會被啓動,使用這個選項的就類似於再別的選項中使用了之前介紹過的Wants=選項;

以上介紹的這連個節是單元文件通用的,也是可選的。接下來我們就來看看.service.timer單元文件中各自必須有的節。

4. Service配置選項

.service單元文件必須有一個[Service]節,用於描述服務和該服務監管的進程的信息。[Service]節可以包含以下選項。

  1. Type= <type>:type可以是simple, exec, forking, oneshot, dbus, notify, idle 中的一種。用於配置此服務的進程啓動類型。

  2. ExecStart= <CMD>:CMD是表示此服務啓動的時候需要執行的命令,例如/bin/echo hello。如果有多個命令,每個命令使用分號隔開。當Type= oneshot的時候,可以指定0到多個命令,否則只能且必須指定一條命令。

  3. ExecStartPre=, ExecStartPost= <CMD>:指定在ExecStart=之前、之後所執行的命令;

  4. ExecCondition= <CMD>:CMD會在ExecStartPre的命令之前執行,命令執行的返回值如果是0或者是SuccessExitStatus指定的值則會繼續執行接下來的命令,如果返回值爲1~254(含)則會跳過接下來的命令但是本段元不會被標記爲failed,如果返回值是255則跳過接下來所有命令並標記此單元爲failed

  5. ExecStop= <CMD>:用於停止由ExecStart啓動的進程,且是在ExecStart執行成功的前提下才會執行;

  6. Restart= <option>:option可以是no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, or always中的一種,分別表示在什麼情況下該服務被殺死了會重啓,例外是如果該服務是被systemctl殺死的則不會在重啓;

  7. SuccessExitStatus=<A list of number>:用於指定返回值是什麼表示成功;

  8. WorkingDirectory=, RootDirectory=, RootImage= : 用於配置命令執行的時候的環境,例如如果不指定工作目錄,則默認工作目錄爲用戶主目錄;

  9. Environment=:用於配置環境,就類似於再終端中使用export命令;;例如:

Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"
  1. EnvironmentFile=:也是設置環境變量,只不過環境變量是通過文件來指定的;

  2. StandardInput=:指定命令的輸入流,可選值爲null, tty, tty-force, tty-fail, data, file:path, socket or fd:name

  3. StandardOutput=, StandardError=:指定命令執行的輸出流、錯誤流的信息,可選的值爲inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, socket or fd:name

5. Timer配置選項

.timer單元文件必須有一個[Timer]節,用於描述定時器的相關信息。[Timer]可以包含以下選項:

  1. Unit= <unit>:用於指定在指定時間需要激活的服務;

  2. OnActiveSec=, OnBootSec=, OnStartupSec=, OnUnitActiveSec=, OnUnitInactiveSec=:用於描述在什麼時間激活Unit=中指定的服務。其值爲帶單位的時間,單位可以是µs, us, ms, s, m, h, d, w, M, y等,也可以是完整的單詞,例如下面的例子中的都是表示系統啓動後一個小時會觸發:

OnBootSec= 1h
OnBootSec= 1hour
  1. OnCalendar=:用於設定觸發的具體的時間。它的一般格式爲DayOfWeek year-month-day hour:minute:second TimeZone,有些部分如果不需要是可以省略的,通配符*表示匹配任何時間,..表示一個區間。一些可能的表示方法如圖2和圖3所示(左邊爲表示方法,右邊爲解析後的實際內容):
    Fig 2 Time  example 1

Fig 3 Time  example 2

  1. OnClockChange=, OnTimezoneChange=<boolean>:用一個布爾值表示當對應事件發生後是否觸發;

  2. WakeSystem=<boolean>:用一個布爾值表示當時間節點到達是否喚醒系統或者使系統休眠;

  3. RemainAfterElapse=<boolean>:用一個布爾值表示此定時器是否可以重複觸發,如果設置成false則此定時器只會觸發一次,默認是重複觸發。

6. systemctl命令

systemctl是一個複雜的命令,這裏只介紹與本文介紹內容相關的命令。可以用systemctl --systemsystemctl --user表示不同模式,如果不指定--system, --user中的任何一個,則默認爲--system

systemctl [OPTIONS...] COMMAND [NAME...]

其中OPTIONS可以是--system, --user, --state=STATE, --type=TYPE, --property=NAME, --job-mode=MODE等,其中STATE和TYPE可以使用--state=help--type=help等列出其可能得取值。

COMMAND可以是list-units, list-timers, start, stop, enable, disable等。

6.1. 與查看Unit狀態相關

查看Unit可以用以下命令:

systemctl  [--user|--sytem] list-units [--type=[service|timer|...]] [--state=[failed|active|running|help|...]]

例如:

# 列出正在運行的 Unit
systemctl  --user list-units
systemctl list-units

# 列出所有Unit,包括沒有找到配置文件的或者啓動失敗的
systemctl  --user list-units --all
systemctl list-units --all

# 列出所有沒有運行的 Unit
systemctl  --user list-units --all --state=inactive
systemctl list-units --all --state=inactive

# 列出所有加載失敗的 Unit
systemctl  --user list-units --failed
systemctl list-units --failed

# 列出所有正在運行的、類型爲 service 的 Unit
systemctl  --user list-units --type=service
systemctl list-units --type=service

6.2. 與操作Unit相關

  1. systemctl [–user] start :啓動某個單元文件;
  2. systemctl [–user] stop :停止某個單元文件;
  3. systemctl [–user] enable :通過單元文件的[Install]節創建符號鏈接;
  4. systemctl [–user] disable :刪除3創建符號鏈接;

6.3. 與守護進程相關

systemctl [–user] daemon-reload

7. systemd如何啓動服務

systemd通過socket和DBus來啓動服務。

8. References

[1] https://www.freedesktop.org/software/systemd/man/systemd.unit.html
[2] https://www.freedesktop.org/software/systemd/man/systemd.service.html#
[3] https://www.freedesktop.org/software/systemd/man/systemd.timer.html#
[4] https://www.freedesktop.org/software/systemd/man/systemd.exec.html#

9. Materials

[1] https://github.com/zmychou/bing-bg-downloader

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章