JAVA通过Crawler实现英语单词的翻译
首先声明一点,这种方法仅限于低频次的交互来获取翻译信息,一旦一秒内大量的哀求会被重定向,那就直接不能用了
如果希望可以批量查询英语单词翻译,可以查看我的下一篇博客。
接着我们上一讲Java如何用HaspMap统计次数并排序详解 - ivanlee717 - 博客园的末端,我们获取到了一个高频次排序好的列表,接下来的任务就是要把这么一大堆的单词举行翻译,我们想要得到每个单词的音标,有什么词性以及对应的翻译。如今我们就来讲讲通过网络来实现单词的翻译。
Java的HttpURLConnection类可以帮助我们发送HTTP哀求,并获取相应的HTTP相应。我们可以设置哀求头、哀求方法、哀求参数等信息,来模仿欣赏器行为。- <dependency>
- <groupId>org.apache.httpcomponents.client5</groupId>
- <artifactId>httpclient5</artifactId>
- <version>5.1</version>
- </dependency>
- <dependency>
- <groupId>org.jsoup</groupId>
- <artifactId>jsoup</artifactId>
- <version>1.15.3</version>
- </dependency>
复制代码 private static final String TRANSLATE_URL = "http://www.baidu.com/s?wd=";这个网页就是我们寻常百度查词的地址
我就希望可以得到两种音标,还有对应的词性和翻译。具体用到的两个依赖,一个是对于网页的解析Jsoup,这个和python的BS4根本原理划一,还有一个就是用于网络哀求的http依赖。- import org.jsoup.Jsoup;
- import java.net.URLEncoder;
- import org.jsoup.nodes.Document;
- import org.jsoup.nodes.Element;
- import org.jsoup.select.Elements;
- import java.net.HttpURLConnection;
- import java.net.URL;
复制代码 第一步我们首先要对查询的文字举行一个编码(这一步在项目里不必要有,因为前序操纵已经确定这个是String类型了)- String encodedWord = URLEncoder.encode(word, StandardCharsets.UTF_8.toString());
- // 编码查询词,防止特殊字符导致的URL解析错误
复制代码 第二步是向url发起链接- // 构建完整的URL,包括查询参数
- URL url = new URL(TRANSLATE_URL + encodedWord);
- // 打开连接
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
复制代码 这里我们将URL封装成了带有网络协议等信息的实例对象来方便我们调用Connection方法。
如今创建好连接之后,我们可以看一下连接里面的数据。
可以看到哀求体里面没有任何的数据段,如许去举行多次读取页面的操纵会让百度感觉很”陌生“,所以如今要去哀求头里加一些数据,让这个访问看起来就是真实的。- // 创建一个可变的 Map 来存储请求头
- Map<String, String> headers = new HashMap<>();
- headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
- headers.put("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
- headers.put("Connection", "keep-alive");
- headers.put("Cookie", "BIDUPSID=2BCC6FC9896B4237256E7EC335CECF0A; PSTM=1726058803....");
- headers.put("Host", "www.baidu.com");
- headers.put("Upgrade-Insecure-Requests", "1");
- headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
复制代码 这些内容都可以直接在自己的网页端查到。我们用一个map来举行存储,然后一次添加到- // 添加自定义请求头
- for (Map.Entry<String, String> entry : headers.entrySet()) {
- connection.setRequestProperty(entry.getKey(), entry.getValue());
- // 设置请求方法为GET
- connection.setRequestMethod("GET");
复制代码 到这一步我们的哀求就算是发送完成了。接下来就是服务器的相应,200就是相应成功了- // 获取响应码
- int responseCode = connection.getResponseCode();
- StringBuilder response = new StringBuilder();
- // 如果响应码为200,表示请求成功
- if (responseCode == HttpURLConnection.HTTP_OK) {
- // 获取响应流
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)
- );
- String line;
- // 读取响应内容
- while ((line = reader.readLine()) != null) {
- response.append(line);
- }
- reader.close();
- }
复制代码 在 Java 的网络编程中,当创建了一个网络连接(比如通过 Socket 连接大概 HttpURLConnection 等创建的连接,这里的 connection 就是代表如许一个已创建的连接对象),可以通过调用 getInputStream() 方法获取这个连接对应的输入流。这个输入流里包含了对方(服务器等)发送过来的数据,不外它是以字节流的情势存在的,原始状态下不太方便直接按文本内容举行读取操纵。
- InputStreamReader 是字节流和字符流之间的桥梁,它用于将字节流(也就是前面获取的 connection.getInputStream() 这个字节输入流)转换为字符流,因为很多时间我们期望处理的是文本字符情势的数据,而不是单纯的字节数组。同时,指定了编码格式为 StandardCharsets.UTF_8,这一点很关键,因为要确保正确地将字节解码成对应的字符。不同的字符编码格式下,相同的字节序列可能表示完全不同的字符,如果不指定正确的编码(服务器通常会按照某种编码格式来发送文本相应,UTF-8 是如今网络通信中很常用的一种编码格式),就可能出现乱码题目,导致后续无法正确解析和处理读取到的文本内容。
又有一个题目,为什么不直接使用 connection.getInputStream() 创建 BufferedReader?
- 字节流与字符流的差别
- connection.getInputStream()返回的是字节流(InputStream)。字节流是以字节为单位来处理数据的,它并不知道这些字节如何组合成字符。而在很多网络通信场景中,如读取网页内容、解析 JSON 或 XML 等文本格式的数据时,我们必要以字符为单位来处理数据。
- 直接使用字节流来读取文本数据会比较麻烦。例如,一个汉字在 UTF - 8 编码下可能占用 3 个字节,如果只按字节读取,很难正确地将这些字节组合成完整的字符举行处理。
- 编码题目
- 没有经过字符流转换直接使用字节流读取文本可能会导致编码题目。不同的字符编码方式(如 UTF - 8、GBK 等)对字符的字节表示是不同的。
- 通过InputStreamReader可以指定编码方式(如StandardCharsets.UTF - 8),将字节流按照正确的编码转换为字符流。如许能确保从网络中读取的文本数据被正确地解码。例如,如果服务器以 UTF - 8 编码发送数据,而客户端没有正确地按照 UTF - 8 解码,就会出现乱码。
- 缓冲和高效读取
- BufferedReader提供了缓冲功能。它内部有一个缓冲区,当读取数据时,会先从缓冲区获取数据,只有当缓冲区为空时才会从底层的输入流(这里是经过InputStreamReader转换后的字符输入流)读取数据并填充缓冲区。
- 这种缓冲机制可以减少与底层数据源(如网络连接)的交互次数,进步读取服从。直接使用connection.getInputStream()没有这种缓冲机制,每次读取操纵可能都会涉及到相对耗时的底层 I/O 操纵。例如,在读取一个较大的文本文件或网络相应中的大量文本内容时,缓冲机制可以显著进步性能。
- 方便的文本读取方法
- BufferedReader提供了方便的readLine()方法,可以逐行读取文本内容。在处理文本数据时,很多时间数据是以行分隔的,如网页的 HTML 代码、设置文件等。
- 直接使用connection.getInputStream()作为字节流没有这种按行读取的便捷方法,必要自己编写复杂的代码来实现按行读取字节流并将其转换为字符的功能
总之,reader读到的信息就是获取的那个页面的源码。我们把他存到一个String对象里面。
接下来才是真正意义上的爬取操纵。- Document doc = Jsoup.parse(response.toString());
- Elements posElements = doc.select("span.part-name_3R3ee");
- Elements En_phoneticElements = doc.select("span.orginal-txt_3dDqw");
- Elements US_phoneticElements = doc.select("sapn.orginal-txt_3dDqw");
复制代码
能看到在html标签里面,英式的发音藏在一个span标签里面,相对应的翻译
我就不一一列举了,用doc.select方式找到对应的标签举行提取。我们可以定义一个DTO大概一个数据库按照类型把他们都存进来。- wordModel.setWord(word);
- wordModel.setEnPronunciation(En_phoneticElements.first().text());
- wordModel.setUsPronunciation(US_phoneticElements.first().text());
复制代码
根据这种写法就得到了我们的音标。但是又有个题目,音标只有一个,但是单词的翻译可能会有很多个,所以单纯的提取词性和翻译就会有题目。
我们得到两个travel的词性,理论上我们必要通过一个for循环来获取。通太过析源码,我们可以看到代码一个词性和对应的翻译都放在一个大的div标签了。
但如今又有一个题目就是这个翻译并不是一个字符串,是一个列表,多个span标签组成的。所以我们想要把翻译搞到一起就得多写两句代码,把列表换成字符串。- Elements translationElements = parent.select("span.mean-text_4MwRe");
- StringBuilder translations = new StringBuilder();
- for (Element element : translationElements) {
- // 去除分号,避免重复
- String text = element.text().replace(";", "").trim();
- if (!text.isEmpty()) {
- translations.append(text).append(";");
- }
- }
复制代码
把数据格式安排明白之后,我们就可以继续存储数据了。- for (Element posElement : posElements) {
- // 获取当前词性的翻译和音标
- Element parent = posElement.parent();
- if (parent != null) {
- Elements translationElements = parent.select("span.mean-text_4MwRe");
- StringBuilder translations = new StringBuilder();
- for (Element element : translationElements) {
- // 去除分号,避免重复
- String text = element.text().replace(";", "").trim();
- if (!text.isEmpty()) {
- translations.append(text).append(";");
- }
- }
- // 创建一个结果映射
- Map<String, String> result = new HashMap<>();
- result.put("translation", translations.toString());
- result.put("pos", posElement.text());
- results.add(result);
- }
- }
复制代码 终极的results当然是多个map映射的列表了,不要嫌麻烦,我们继续转换数据,把得到的词性翻译列表转换成一个可以直观的字符串。- StringBuilder combinedResult = new StringBuilder();
- for (Map<String, String> result : results) {
- String pos = result.get("pos");
- String translation = result.get("translation");
- combinedResult.append(pos).append(translation).append(" ");
- }
- wordModel.setDescription(combinedResult.toString());
复制代码
如许的话,我们就把所有的一个浅易的爬虫式的单词翻译讲清楚了。我们后续会继续讲如何把查询到的翻译存到本地数据库。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |