如何使用Lightrun在生產環境中調試jsoup Java代碼
譯文譯者 | 李睿
審校 | 孫淑娟
網站抓取(Scraping)是一門安全性比較薄弱的學科。人們經常使用服務器來解決,而調試和解決這些問題非常困難,至少現在是這樣。
抓取采用現代瀏覽器構建的網站比十年前更具挑戰性。jsoup是一個方便的API,它通過DOM遍歷、CSS選擇器、類似JQuery的方法等使抓取網站變得簡單。但這并非沒有挑戰,因為每個抓取的API都可能是一顆定時炸彈。
現實世界的HTML是脆弱的。因為它不是一個文檔化的API,所以會在沒有通知的情況下進行更改。當Java程序在抓取方面失敗時,可能就面臨更多的麻煩。在某些情況下,這是一個簡單的問題,可以在本地復制并部署。但在本地測試用例中,DOM樹中的一些細微變化可能更難觀察到。在這些情況下,需要在推動更新之前了解解析樹中的問題。否則,開發的軟件產品可能會損壞。
什么是jsoup?JavaHTML解析器
在深入了解調試jsoup的具體細節之前,先回答上面的問題,并討論jsoup背后的核心概念。
jsoup網站將其定義為:jsoup是一個用于處理真實世界HTML的Java庫。它使用HTML5 DOM方法和CSS選擇器提供了一個非常方便的API,用于獲取URL以及提取和操作數據。
jsoup實現了WHATWG HTML5規范,并將HTML解析為與現代瀏覽器相同的DOM。
考慮到這一點,可以直接從同一個網站上獲取一個簡單的示例:
Java
1 Document doc = Jsoup.connect("https://en.wikipedia.org/").get();
2 log(doc.title());
3 Elements newsHeadlines = doc.select("#mp-itn b a");
4 for (Element headline : newsHeadlines) {
5 log("%s\n\t%s",
6 headline.attr("title"), headline.absUrl("href"));
7 }
這段代碼片段摘自維基百科的標題。在上面的代碼中,可以看到幾個有趣的特性:
- 與URL的連接實際上是無縫的——只需將字符串URL傳遞給connect方法。
- 某些子元素有特殊情況。例如。Title被公開為一個簡單的方法,它返回一個字符串而不從DOM樹中選擇。
- 可以使用非常復雜的選擇器語法來選擇條目。
簡單的jsoup測試
為了演示調試,創建了一個簡單的演示。
XML
1 <dependency>
2 <groupId>org.jsoup</groupId>
3 <artifactId>jsoup</artifactId>
4 <version>1.14.3</version>
5 </dependency>
可以使用以下Maven依賴項將jsoup安裝到任何Java程序中。Maven將無縫下載jsoupjar:
Java
1 public Set<String> listLinks(String url, boolean includeMedia) throws IOException {
2 Document doc = Jsoup.connect(url).get();
3 Elements links = doc.select("a[href]");
4 Elements imports = doc.select("link[href]");
5
6 Set<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
7 if(includeMedia) {
8 Elements media = doc.select("[src]");
9 for (Element src : media) {
10 result.add(src.absUrl("src"));
11 //result.add(src.attr("abs:src"));
12 }
13 }
14
15 for (Element link : imports) {
16 result.add(link.absUrl("abs:href"));
17 }
18
19 for (Element link : links) {
20 result.add(link.absUrl("abs:href"));
21 }
22
23 return result;
24 }
本段代碼可以獲取輸入的字符串URL。也可以使用輸入流,但這在解析相對URL時會稍微復雜一些(無論如何都需要一個基本URL)。然后搜索具有src屬性的鏈接和對象。最后代碼將它們全部添加到一個集合中,以保持條目的排序和唯一性。
我們使用以下代碼將其公開為Web服務:
1
2 public class ParseLinksWS {
3 private final ParseLinks parseLinks;
4
5 public ParseLinksWS(ParseLinks parseLinks) {
6 this.parseLinks = parseLinks;
7 }
8
9 ("/parseLinks")
10 public Set<String> listLinks( String url, (required = false) Boolean includeMedia) throws IOException {
11 return parseLinks.listLinks(url, includeMedia == null ? true : includeMedia);
12 }
13 }
一旦運行應用程序,就可以通過一個簡單的curl命令使用它:
Java
1 curl -H "Content-Type: application/json" "http://localhost:8080/parseLinks?url=https%3A%2F%2Flightrun.com"
這將打印出Lightrun主頁中引用的URL列表。
調試內容失敗
當元素對象更改時,會出現典型的字符串抓取問題。例如,維基百科可以更改其頁面的結構,而上面的選擇方法可能會失敗。這通常是一個微妙的失敗,是在處理嵌套節點元素和文檔間依賴關系時。例如Java對象層次結構中缺少DOM元素,這可能會觸發選擇方法的失敗。大多數開發人員通過記錄大量數據來解決這個問題。產生這個問題的原因有三個:
- 日志數據量大——它們既難以閱讀,又非常昂貴。
- 隱私/GDPR違規——被抓取的網站可能包含特定用戶的私人信息。
- 在最初實施抓取之后,抓取的站點可能會更改為包含私人信息。記錄這些私人信息可能會違反各種隱私法規。
如果沒有足夠的日志并且無法在本地重現問題,就會陷入到添加日志、構建、測試、部署、重現這樣的重復循環中。
Lightrun提供了一種更好的方法。只需直接在生產中跟蹤特定故障、驗證問題,并創建適用于一個部署的修復程序。
注:本文假設安裝了Lightrun并了解其背后的基本概念。如果沒有,可以查看文檔。
在瀏覽器DOM中找到自己的方式
假設不知道從何開始,那么jsoup API是一個很好的起點。它可以帶回用戶代碼。很酷的是,無論代碼如何都會有效。通過深入研究API調用,可以找到快照的正確行/文件。
在此處按ctrl鍵(在Mac上使用Meta-click)選擇方法調用:
Java
1 Elements links = doc.select("a[href]");
它帶到了Element類。在其中,按ctrl鍵單擊選擇器“select”方法,可以放置一個條件快照來查看每個執行“a[href]”查詢的情況:
這可以顯示執行該查詢的方法/行:
這對縮小文檔對象層次結構中的一般問題區域有很大幫助。
有時采用快照可能還不夠,可能需要使用日志。日志記錄的優點是可以生成大量信息,但僅針對特定情況和按需生成。
日志的價值在于,它們能夠以非常類似于單步執行代碼的方式跟蹤問題。放置快照的位置對于日志來說是有問題的。我們知道發送的查詢,但還沒有返回的值。可以用日志輕松解決這個問題。首先,添加一個包含以下文本的日志:
"Executing query {query}"
然后,要找出返回了多少條目,只需轉到調用者(我們知道這要歸功于快照中的堆棧)并在那里添加以下日志:
Links query returned {links.size()}
這會產生以下日志,讓我們看到有147個a[href]鏈接。這樣做的好處是額外的日志與場景中預先存在的日志交錯:
Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Executing query a[href]
Feb 02, 2022 11:25:27 AM com.lightrun.demo.jsoupdemo.service.ParseLinks listLinks
INFO: LOGPOINT: Links query returned 147
Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Executing query link[href]
Feb 02, 2022 11:25:27 AM org.jsoup.select.Selector select
INFO: LOGPOINT: Executing query [src]
避免安全和GDPR問題
GDPR和安全問題可能是將用戶信息泄漏到日志中的問題。這可能是一個主要問題,Lightrun可以幫助顯著降低這種風險。
Lightrun提供了兩種可能的解決方案,可以在適用時串聯使用。
(1)日志管道
GDPR的最大問題是日志攝取。如果記錄私人用戶數據,然后將其發送到云端,它會在那里保存很長時間,并且事后很難找到,也很難修復。
Lightrun提供了將Lightrun的所有注入日志直接通過管道傳輸到IDE的能力。這樣做的好處是可以消除可能使用日志的其他開發人員的干擾。它還可以跳過攝取(可選)。
如果僅將日志發送到插件,需要將管道模式選擇為“插件”。
(2)PII減少/阻止列表
個人身份信息(PII)是GDPR法規的核心,也是一個主要的安全風險。而企業中的惡意開發人員可能希望使用Lightrun來竊取用戶信息。阻止列表阻止開發人員在特定文件中放置操作。
驗證個人身份信息(PII) 可以減少從日志中隱藏匹配特定模式的信息(例如信用卡格式等)。這可以由管理員角色在Lightrun Web界面中定義。
結語
對于Java內容抓取,jsoup顯然是領導者。使用jsoup進行開發遠遠超過字符串操作,甚至在處理連接方面。除了獲取文檔對象外,它還處理DOM元素和腳本所需的復雜方面。
抓取是一項有風險的業務。當網站發生輕微變化時,它可能會在眨眼間崩潰。更糟糕的是,它可能會以奇怪的方式影響某些用戶,而這些方式不可能在本地復制。
而有了Lightrun,可以直接在生產環境中調試此類故障,并快速發布工作版本。
原文標題:??Debugging jsoup Java Code in Production Using Lightrun??,作者:Shai Almog