ru_html_extractor/
lib.rs

1use std::collections::HashMap;
2use regex::{Regex, Error as RegexError};
3
4fn clear_a_and_tag(line: &str) -> String {
5    let re_a = Regex::new(r"<a[^>]*>.*?</a>").unwrap();
6    let cleaned_text = re_a.replace_all(line, "");
7
8    let re_tags = Regex::new(r"<[^>]*>").unwrap();
9    re_tags.replace_all(&cleaned_text, "").to_string()
10}
11
12fn check_nav_link(line: &str) -> bool {
13    let re_a = Regex::new(r"<a[^>]*>.*?</a>").unwrap();
14    if re_a.is_match(line) {
15        let cleaned_text = clear_a_and_tag(line);
16        cleaned_text.trim().len() < 1
17    } else {
18        false
19    }
20}
21
22fn check_not_pure_tag(line: &str) -> bool {
23    let re_tag = Regex::new(r"<[^>]*>").unwrap();
24    if !line.is_empty() {
25        let cleaned_text = re_tag.replace_all(line, "");
26        cleaned_text.trim().len() > 0
27    } else {
28        false
29    }
30}
31
32fn window_group(marked: &Vec<usize>, window_size: usize) -> Vec<Vec<usize>> {
33    let mut result = Vec::new();
34    let mut lst_num = marked[0];
35    let mut current:Vec<usize> = vec![lst_num];
36    for num in &marked[1..marked.len()] {
37        let diff = *num - lst_num;
38        if diff <= window_size {
39            lst_num = *num;
40            current.push(lst_num);
41        }else{
42            result.push(current.clone());
43            current = Vec::new();
44            lst_num = *num;
45            current.push(lst_num);
46        }
47    }
48    result
49}
50
51/// Process HTML content to extract the pure text
52///
53/// # Examples
54///
55/// ```
56/// let html = r#"
57/// <!DOCTYPE html>
58/// <html>
59/// <head>
60/// <meta charset="utf-8" />
61/// </head>
62/// <body itemscope itemtype="http://schema.org/WebPage">
63/// <div class="name">文艺时代</div>
64/// <div class="breadcrumb" itemprop="breadcrumb">当前位置:<a href="/">首页</a> ›
65/// <a href="/c/zhichang.html">职场小说</a> ›
66/// <a href="/n/wenyishidai/">《文艺时代》</a>
67/// </div>
68/// <div class="main" itemscope itemtype="http://schema.org/Article">
69/// <h1 class="headline" itemprop="headline">第1章 褚青</h1>
70/// <p>第一章 褚青</p><p>1997年,京城。</p><p>正是初春,天气还很寒冷,街上的行人还没脱去冬装。</p><p>褚青裹了裹身上的皮夹克,蹲在马路边。</p><p>这件皮夹克是去年最流行的款式,青年们的最爱,价格不菲。连抽烟都按根算的褚青当然买不起,这是他抢来的。</p><p>原主人应该是个败家子,不知怎地在夹克上划了一道口子,在领口处,很细小的口子,就惹了主人嫌弃,被直接扔掉。</p><p>当时褚青和另一位捡垃圾的老伙伴同时盯上了这块肥肉,最后还是他仗着年轻体壮抢到手,跟那个老伙伴也从此友尽。</p><p>他觉得很值,以他的收入,或许要干上一个月才能买这么一件。</p><p>不过是一起喝酒扯皮的朋友,没了也就没了。</p><p>天有些阴,不见太阳。无论车辆还是行人,都显得很慵懒,连骑车的人蹬脚蹬都轻飘飘的。</p><p>刚过完年,一切还没开始呢。</p><p>褚青已经四年没回家了,确切的说,他重生到这个年代已经两个月了。</p>
71/// </div>
72/// <div class="list_page">
73/// <span>上一页</span><span><a href="/n/wenyishidai/list.html" rel="contents" itemprop="url">目录</a></span><span><a href="/n/wenyishidai/2.html" itemprop="url" rel="next">下一页</a></span>
74/// </div>
75/// </div>
76/// <div class="footer">
77/// <p class="copyright">&copy; 2024 quanben.io</p>
78/// </div>
79/// <script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');ga('create', 'UA-39695403-5', 'auto');ga('send', 'pageview');</script>
80/// </body>
81/// </html>
82/// "#;
83/// let pure_content = process(html);
84/// let result = "第一章 褚青1997年,京城。正是初春,天气还很寒冷,街上的行人还没脱去冬装。褚青裹了裹身上的皮夹克,蹲在马路边。这件皮夹克是去年最流行的款式,青年们的最爱,价格不菲。连抽烟都按根算的褚青当然买不起,这是他抢来的。原主人应该是个败家子,不知怎地在夹克上划了一道口子,在领口处,很细小的口子,就惹了主人嫌弃,被直接扔掉。当时褚青和另一位捡垃圾的老伙伴同时盯上了这块肥肉,最后还是他仗着年轻体壮抢到手,跟那个老伙伴也从此友尽。他觉得很值,以他的收入,或许要干上一个月才能买这么一件。不过是一起喝酒扯皮的朋友,没了也就没了。天有些阴,不见太阳。无论车辆还是行人,都显得很慵懒,连骑车的人蹬脚蹬都轻飘飘的。刚过完年,一切还没开始呢。褚青已经四年没回家了,确切的说,他重生到这个年代已经两个月了。";
85/// assert_eq!(result, pure_content);
86/// ```
87pub fn process(html: &str) -> Result<String, RegexError> {
88    let body_re =Regex::new(r"(?is)<body[^>]*>(.*?)</body>")?;
89    let script_style_re = Regex::new(r"<script[^>]*>.*?</script>|<style[^>]*>.*?</style>")?;
90    let body_content = body_re.captures(html).map(|c| c.get(1).map(|m| m.as_str()).unwrap_or("")).unwrap_or("");
91    let cleaned_content = script_style_re.replace_all(body_content, "");
92    let mut cleaned_map = HashMap::new();
93    let mut marked = Vec::new();
94    for (line_no, line) in cleaned_content.lines().enumerate() {
95        if !check_nav_link(line) {
96            if check_not_pure_tag(line) {
97                marked.push(line_no);
98                cleaned_map.insert(line_no, clear_a_and_tag(line).trim().to_string());
99            }
100        }
101    }
102    let groups = window_group(&marked, 2usize);
103    let mut max_weight = 0;
104    let mut max_weight_idx = -1;
105    for (gp_no, gp) in groups.iter().enumerate() {
106        let weight = gp.iter().map(|&no| cleaned_map[&no].len()).sum();
107        if max_weight <= weight {
108            max_weight = weight;
109            max_weight_idx = gp_no as i32;
110        }
111    }
112    Ok(groups[max_weight_idx as usize].iter().map(|&line_no| cleaned_map[&line_no].clone()).collect::<Vec<String>>().join("\n"))
113}
114
115
116#[cfg(test)]
117mod tests {
118    use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
119    use super::*;
120
121    #[test]
122    fn check_process_len() {
123        let client = reqwest::blocking::Client::new();
124        // 设置请求头以模拟Chrome浏览器
125        let headers = {
126            let mut headers = HeaderMap::new();
127            headers.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"));
128            headers
129        };
130        // 发送GET请求
131        if let Ok(response) = client.get("https://quanben.io/n/wenyishidai/1.html").headers(headers).send() {
132            // 检查响应状态码
133            if response.status().is_success() {
134                if let Ok(html) = response.text() {
135                    let result = process(html.as_str());
136                    assert!(result.is_ok());
137                    if let Ok(result) = result {
138                        println!("content:\n{}", result.as_str());
139                        return assert!(result.len() > 10);
140                    }
141                }
142            }
143        }
144        assert_eq!("a", "b");
145    }
146}