使用Html Unit结合XPath无法获取预期结果的技术问题
问题描述
我尝试用Html Unit通过谷歌搜索爬取《印度教徒报》2019年6月24日的头条新闻,能成功获取搜索结果页面,但使用XPath提取头条新闻链接时始终失败,(HtmlAnchor) searchResultPage.getByXPath(xpath)返回null。我确认浏览器中看到的页面结构对应的XPath是正确的,搜索结果页面显示第二条结果为目标新闻链接。
出问题的代码片段:
HtmlPage page = client.getPage("https://www.google.co.in"); List<HtmlForm> allForms = page.getForms(); System.out.println("No of Forms Detected : --- " + allForms.size()); HtmlForm searchForm = allForms.get(0); HtmlTextArea searchTextArea = searchForm.getTextAreaByName("q"); searchTextArea.setText("24 June 2019 news, the hindu"); HtmlInput gSearch = searchForm.getInputByName("btnK"); HtmlPage searchResultPage = (HtmlPage) gSearch.click(); client.waitForBackgroundJavaScript(5000 * 2); System.out.println(searchResultPage.asNormalizedText()); String xpath = "//*[@id='web']/ol/li[2]/div/div[1]/h3/a"; HtmlAnchor topNewsLink = (HtmlAnchor) searchResultPage.getByXPath(xpath).get(0); HtmlPage postPage = topNewsLink.click(); System.out.println(postPage.asNormalizedText());
问题原因及解决方案
核心原因
- 谷歌页面动态渲染差异:Html Unit的JavaScript渲染能力和真实浏览器有差距,即使调用
waitForBackgroundJavaScript,页面实际渲染后的DOM结构可能和浏览器开发者工具中看到的不一致,导致依赖固定层级的XPath失效。 - XPath依赖不稳定的页面结构:谷歌搜索页面的ID(如
web)、层级结构会频繁更新,固定位置(li[2])也可能因搜索结果排序变化而失效。 - 搜索框类型错误:谷歌搜索框实际是
<input>元素,而非代码中使用的<textarea>,这可能导致搜索执行不完整,返回的结果页面不符合预期。
解决步骤
1. 修正搜索框元素类型
将HtmlTextArea改为HtmlTextInput,并使用setValueAttribute设置搜索内容:
HtmlTextInput searchTextInput = searchForm.getInputByName("q"); searchTextInput.setValueAttribute("24 June 2019 news, the hindu");
2. 优化页面等待与渲染
延长JavaScript等待时间,或刷新页面确保DOM完全加载:
client.waitForBackgroundJavaScript(10000); searchResultPage = (HtmlPage) searchResultPage.refresh();
3. 使用鲁棒的XPath定位
放弃依赖固定层级和ID,改用目标域名、父容器类名等稳定特征定位:
// 匹配包含thehindu.com链接的搜索结果项 String xpath = "//div[@class='g']//a[contains(@href, 'thehindu.com')]"; List<HtmlAnchor> links = (List<HtmlAnchor>) searchResultPage.getByXPath(xpath);
4. 调试页面结构
打印页面XML对比真实DOM,确认XPath是否匹配:
System.out.println(searchResultPage.asXml());
5. 优化Html Unit配置
模拟主流浏览器,禁用不必要的特性减少渲染差异:
WebClient client = new WebClient(BrowserVersion.CHROME); client.getOptions().setJavaScriptEnabled(true); client.getOptions().setCssEnabled(false); client.getOptions().setThrowExceptionOnScriptError(false);
修改后的完整代码示例
import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlTextInput; import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlAnchor; import java.util.List; public class HinduNewsCrawler { public static void main(String[] args) { try (WebClient client = new WebClient(BrowserVersion.CHROME)) { // 配置客户端 client.getOptions().setJavaScriptEnabled(true); client.getOptions().setCssEnabled(false); client.getOptions().setThrowExceptionOnScriptError(false); client.getOptions().setThrowExceptionOnFailingStatusCode(false); // 访问谷歌搜索页 HtmlPage page = client.getPage("https://www.google.co.in"); // 获取搜索表单并输入关键词 List<HtmlForm> allForms = page.getForms(); System.out.println("检测到的表单数量: --- " + allForms.size()); HtmlForm searchForm = allForms.get(0); HtmlTextInput searchTextInput = searchForm.getInputByName("q"); searchTextInput.setValueAttribute("24 June 2019 news, the hindu"); // 执行搜索 HtmlInput gSearch = searchForm.getInputByName("btnK"); HtmlPage searchResultPage = (HtmlPage) gSearch.click(); // 等待页面渲染并刷新 client.waitForBackgroundJavaScript(10000); searchResultPage = (HtmlPage) searchResultPage.refresh(); // 定位目标链接 String xpath = "//div[@class='g']//a[contains(@href, 'thehindu.com')]"; List<HtmlAnchor> links = (List<HtmlAnchor>) searchResultPage.getByXPath(xpath); if (!links.isEmpty()) { HtmlAnchor topNewsLink = links.get(0); HtmlPage postPage = topNewsLink.click(); client.waitForBackgroundJavaScript(5000); System.out.println(postPage.asNormalizedText()); } else { System.out.println("未找到《印度教徒报》的相关新闻链接"); } } catch (Exception e) { e.printStackTrace(); } } }
内容的提问来源于stack exchange,提问作者smart987
相关产品推荐
相关产品推荐

