Java中为什么会出现403 Forbidden错误而网页浏览器却不会?

66

我正在编写一个小型的Java程序,以获取给定Google搜索词的结果数量。但是出现了403 Forbidden错误,而在Web浏览器中我可以得到正确的结果。以下是代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;


public class DataGetter {

    public static void main(String[] args) throws IOException {
        getResultAmount("test");
    }

    private static int getResultAmount(String query) throws IOException {
        BufferedReader r = new BufferedReader(new InputStreamReader(new URL("https://www.google.com/search?q=" + query).openConnection()
                .getInputStream()));
        String line;
        String src = "";
        while ((line = r.readLine()) != null) {
            src += line;
        }
        System.out.println(src);
        return 1;
    }

}

并且出现了错误:

Exception in thread "main" java.io.IOException: Server returned HTTP response code: 403 for URL: https://www.google.com/search?q=test
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)
    at DataGetter.getResultAmount(DataGetter.java:15)
    at DataGetter.main(DataGetter.java:10)
为什么会这样做?

@Perception 嗯... SSL 端点是什么?(抱歉,我对这种东西一窍不通) - tckmn
2
SSL(安全套接字层)是确保客户端和服务器之间传递的数据安全的一种方法。 SSL终端点是常规URL,但使用https而不是http。 使用SSL比常规http更复杂,因为客户端和服务器之间需要握手。 在您的情况下是不必要的,因为您可以只使用Google的“正常”http终端点(http;//www.google.com/search)。 - Perception
@Perception 如果我使用普通的http://,同样的事情会发生。 - tckmn
将您正在使用的查询添加到问题中。 - Perception
4个回答

128

你只需要设置用户代理标头即可使其工作:

URLConnection connection = new URL("https://www.google.com/search?q=" + query).openConnection();
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
connection.connect();

BufferedReader r  = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));

StringBuilder sb = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
    sb.append(line);
}
System.out.println(sb.toString());

从你的异常堆栈可以看出,SSL已经被透明地处理了。

然而,获取结果金额并不是这么简单的,接下来你需要通过获取cookie并解析重定向令牌链接来伪装成浏览器。

String cookie = connection.getHeaderField( "Set-Cookie").split(";")[0];
Pattern pattern = Pattern.compile("content=\\\"0;url=(.*?)\\\"");
Matcher m = pattern.matcher(response);
if( m.find() ) {
    String url = m.group(1);
    connection = new URL(url).openConnection();
    connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
    connection.setRequestProperty("Cookie", cookie );
    connection.connect();
    r  = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")));
    sb = new StringBuilder();
    while ((line = r.readLine()) != null) {
        sb.append(line);
    }
    response = sb.toString();
    pattern = Pattern.compile("<div id=\"resultStats\">About ([0-9,]+) results</div>");
    m = pattern.matcher(response);
    if( m.find() ) {
        long amount = Long.parseLong(m.group(1).replaceAll(",", ""));
        return amount;
    }

}

运行完整代码后,我得到了2930000000L作为结果。


老兄,我欠你一桶啤酒,这是我的问题的完美解决方案!谷歌可以使用这种方法限制/减缓您的搜索结果吗? - benscabbia
@gudthing 的限流是基于 IP 的,所以不是关于方法而是你是否更改了你的 IP :-) - Esailija
我明白了!简单的路由器重启(用于更改WAN)将解决问题:)。再次感谢!! - benscabbia
@Esailija 变量 response 应该包含什么内容? - Harshita Sethi
1
完整的代码链接已失效。它能重新托管在一个没有过期时间的服务上吗? - killjoy
显示剩余4条评论

6
对于我来说,通过添加头部信息"Accept": "*/*"解决了问题。

这个对我起作用了,但是我不确定为什么它起作用了。你能否更详细地解释一下吗? - KimchiMan
2023年无法正常工作,请查看https://stackoverflow.com/questions/77227173/jsoup-connect-is-throwing-403-for-valid-login-credential-cookie-value - undefined

3

您可能没有设置正确的头部信息。在浏览器中使用LiveHttpHeaders(或相似工具)查看浏览器发送的头部信息,然后在您的代码中模拟这些头部信息。


我尝试了"https://www.google.com/search?q=" + query + "&rlz=1C1RNNN_enUS371&aq=f&oq=" + query + "&sugexp=chrome,mod=6&sourceid=chrome&ie=UTF-8",但仍然不起作用。 - tckmn
1
@PicklishDoorknob,你添加了一个查询字符串参数,但你没有更改头文件。你可以使用 URLConnection 对象上的 .setRequestProperty() 方法来设置头文件。 - Esailija
这是一篇关于添加请求头的SO文章:https://dev59.com/J3RB5IYBdhLWcg3w4bEo - Kevin Day
请你帮忙检查一下这个链接:https://stackoverflow.com/questions/77227173/jsoup-connect-is-throwing-403-for-valid-login-credential-cookie-value - undefined

0

这是因为该网站使用了SSL。尝试使用Jersey HTTP客户端。你可能还需要学习一些关于HTTPS和证书的知识,但我认为Jersey可以忽略与实际安全有关的大部分细节。


1
不是这样的,它只是通过仿真浏览器的HTTP标头工作,就像@KevinDay在他的答案中所说的那样。 - Esailija
3
@Ben Brunk - 这里有一个很好的教训 - 在核心部分,所有编程都是由一层又一层的抽象构建而成的。理解低层级别非常有用。使用像你所描述的更高级别的客户端可能有效,但只是因为它正在进行低级别的调用,如果你选择,你自己也可以进行这样的调用。我永远不会忘记,通过 telnet 客户端与 Web 服务器交互并手动编写 HTTP 请求时它是如何启发我的。加油! - Kevin Day
其实,我仍然不确定为什么那段代码能够工作,因为通常你需要将站点的公共证书添加到本地Java密钥库中才能像那样使用SSL,即使是使用URLConnection,所以这个URL有些问题。另外,是什么让你认为我从未使用telnet连接到网站?我以此为生,我经常忘记这个网站上有很多学生或业余程序员。我只是尽力帮助。 - user785262
如果网站使用的证书具有到包含在JAVA的cacerts信任存储中(位于jdk\jre\lib\security)的CA的信任链,则不需要显式添加网站证书。 - user472749

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接