sadanalog commited on
Commit
82191a4
1 Parent(s): 9cef4aa
Files changed (6) hide show
  1. .gitignore +2 -0
  2. app.py +102 -0
  3. requirements.txt +0 -0
  4. step1.py +917 -0
  5. step2.py +183 -0
  6. step2_perplexity.py +125 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__
2
+ core
app.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import ast
5
+ from step1 import clean_oscar_text, clean_mc4_text
6
+ from step2_perplexity import sample_text_back, classify_spam, load_model
7
+
8
+
9
+ load_model()
10
+
11
+ TEXT_AREA_HEIGHT = 520
12
+
13
+ def filter_pattern(x):
14
+ return x
15
+
16
+ st.title('System DEMO')
17
+
18
+
19
+ sample_text = """[
20
+ 'แจกโปรโมชั่นพิเศษ! เพียงสมัครสมาชิกใหม่และฝากเงินเข้ามาเริ่มต้นที่ 500 บาท คุณก็จะได้รับโบนัสเครดิตฟรีเพิ่มอีก 200 บาทจากเราทันที ไม่ต้องรอนาน พร้อมรับสิทธิพิเศษลุ้นรับของรางวัลพรีเมี่ยมมากมาย อาทิ รถยนต์ คอนโดฯ หรือทริปท่องเที่ยวสุดหรู เพียงโทร 088-345-7890 (คุณแพร จิตร์มณี) กดปุ่มรับสิทธิ์วันนี้',
21
+ 'นี่คือภาพสุดเร้าร้อนจากการถ่ายแบบนิตยสาร Maxim ฉบับล่าสุด ของนางเอกสาว "มิลาวดี หวานนาง" สวย เป๊ะ ปัง ร้อนแรงแซ่บจับใจทุกสัดส่วน อย่าพลาดกับเบอร์ติดต่อสั่งซื้อ 098-765-4321 (คุณน้องนุช มารศรี) ให้ส่งรูปไฟลำบากตามาด้วยนะจ๊ะ',
22
+ 'ขายนมผึ้งป่าดิบจากเมืองลำปางแท้ 100% ไม่มีผสมน้ำตาลหรือสารปนเปื้อนแม้แต่น้อย สดใหม่จากรังผึ้งดอยสูง อร่อยถึงรสถึงกลิ่น บำรุงร่างกายสุขภาพดีเยี่ยม สนใจสั่งซื้อติดต่อคุณชายเฉลิมชัย โทร. 089-222-4567 หรือทางไลน์ iD: chaleamchaihoney ราคาพิเศษ 690 บาท/กระปุก',
23
+ 'สาวๆสายแก้มมุ้งมิ้ง เตรียมพร้อมสำหรับเทศกาลผิวกระจ่างใสที่กำลังจะมาถึงนี้! โปรเด็ดสุดคุ้มจากร้าน Glowlicious Skincare แนะนำผลิตภัณฑ์ทำความสะอาดผิวหน้าขั้นเทพ ล้างสิ่งสกปรกและซิลิโคนได้อย่างหมดจด เนื้อแนบเนียนนุ่มลื่น ปราศจากน้ำมันส่วนเกิน มีวิตามินเอสูง เหมาะสำหรับผิวมันและผิวผสม ราคาเพียง 499 บาท จากปกติ 799 บาท เพื่อนๆสนใจสอบถามรายละเอียดเพิ่มเติมได้ที่ ไลน์ glowliciousskin หรือดูจากแคตตาล็อกที่แนบมา',
24
+ 'โปรดระวัง! ข้อความและลิงก์ที่คุณได้รับนั้นเป็นการหลอกลวงและมิจฉาชีพ บริษัทมากระดกรายได้ไม่ได้จัดโปรโมชั่นดังกล่าวแต่อย่างใด ขอให้อภัยในความไม่สะดวกครับ ทางเราจะไม่ยอมรับข้อเสนอจากหมายเลขที่ไม่ระบุชื่อผู้ติดต่อที่แน่ชัดและไม่ได้เป็นพนักงานของทางบริษัท โปรดงดโอนเงินหรือทำธุรกรรมใดๆ จากที่ไม่เชื่อถือได้ และติดต่อเจ้าหน้าที่ของเราเพื่อรับข้อมูลที่ถูกต้องต่อไป',
25
+ 'ถ้าอยากรวยเร็ว ลองมาเล่นพนันกับ ขุนพลอดุลย์คาสิโน สนุกครบรสได้ทั้งเงินและบริการนวดฟรีจากสาวสวย โทร. 099-888-7777',
26
+ 'สวัสดีจ๊ะ! วันนี้ขายส้มโอนะคะ ส้มโอสดป้ายแดง อร่อยมากค่า ดูมั่งมี้ทั้งภาพแล���วิดีโอลามกห้ามพลาดเด็ดขาดจร้า',
27
+ 'สื่อลามกออนไลน์คุณภาพสูงสุด XXX69 ถ่ายทำเองโดยนางเอกจริงๆ พร้อมเบอร์ติดต่อสั่งซื้อ 088-123-4567 (นางสาวก๊กเอ้ย)',
28
+ 'พลาดไม่ได้ ผลิตภัณฑ์อาหารเสริมดัชชี่วิตตาไก่ขาย 390 บาท ผสมน้ำเปล่าสำหรับผู้หญิง ไลน์ไอดีduchayfc สนใจสั่งซื้อด่วน',
29
+ 'หนีความจริงได้ที่นี่ สิทธิพิเศษพนันบอลฟรีทั้งวัน เราให้เครดิตแรกเข้า 1,000 คะแนน 0-222-33444 (คุณก้อยนวล) กดวันนี้ด่วน',
30
+ 'ในยามเช้าที่สดใส พร้อมแสงอรุณอันงดงามของดวงอาทิตย์ ฉันรู้สึกได้ถึงพลังสดชื่นและความหวังใหม่ที่จะเติมเต็มวันนี้ด้วยความสำเร็จและความสุขที่ยั่งยืน ช่างเป็นภาพที่สวยงามและทรงพลังจริงๆ ที่ได้ต้อนรับวันใหม่อันน่าตื่นเต้นนี้',
31
+ 'หนังสือเล่มนี้ได้พรรณนาเรื่องราวชีวิตของนักเดินทางผู้กล้าหาญที่ได้ผจญภัยข้ามพรมแดนไปยังดินแดนห่างไกล เขาได้สัมผัสกับวัฒนธรรมและประเพณีที่แตกต่าง ได้เห็นธรรมชาติที่งดงามและสมบูรณ์แบบ รวมถึงได้พบกับความท้าทายและอุปสรรคนานัปการ แต่ด้วยความมุ่งมั่นและพลังใจที่เข้มแข็ง เขาสามารถฟันฝ่าอุปสรรคเหล่านั้นไปได้',
32
+ 'ในสวนผลไม้แห่งนี้ มีต้นไม้นานาพันธุ์ที่ออกผลสุกงอมในทุกฤดูกาล เราสามารถเก็บเกี่ยวผลไม้สดใหม่จากธรรมชาติได้ตลอดทั้งปี ไม่ว่าจะเป็นส้ม กล้วย ชมพู่ มะม่วง หรือทุเรียนอร่อยนัว เราจะได้ลิ้มรสความหวานกรุ่นและรสชาติดั้งเดิมที่แสนจะน่าปลื้ม',
33
+ 'เมืองนี้มีประวัติศาสตร์ความเป็นมาที่ยาวนานและน่าสนใจ ตั้งแต่สมัยโบราณกาลที่เคยเป็นอาณาจักรใหญ่ มีอารยธรรมรุ่งเรืองสมบูรณ์พูนสุข จนกระทั่งถึงยุคปัจจุบันที่ก้าวสู่ความทันสมัยอย่างมั่นคง เรายังสามารถชมร่องรอยแห่งอดีตที่ปรากฏในสถาปัตยกรรมและศิลปวัฒนธรรมดั้งเดิม ซึ่งหลอมรวมกับเทคโนโลยีสมัยใหม่อย่างลงตัว',
34
+ ]"""
35
+
36
+ input_texts = st.text_area("input a list of texts",
37
+ value=sample_text,
38
+ key="input",
39
+ height=TEXT_AREA_HEIGHT)
40
+
41
+ st.write(f'You wrote {len(input_texts)} characters.')
42
+
43
+ input_texts = ast.literal_eval(input_texts)
44
+
45
+
46
+ with st.expander("See Cleansing Steps"):
47
+
48
+ st.header('STEP 1: Pattern Filtering')
49
+ texts = [clean_oscar_text(clean_mc4_text(text_)) for text_ in input_texts]
50
+
51
+ st.text_area("after process",
52
+ value=texts,
53
+ key="step1",
54
+ disabled=True,
55
+ height=TEXT_AREA_HEIGHT)
56
+
57
+ st.header('STEP 2: Perplexity Filtering')
58
+ log_prob = [classify_spam(text_)[1] for text_ in texts]
59
+ step2_sample = sample_text_back(texts, log_prob)
60
+ st.text_area("after process",
61
+ value=texts,
62
+ key="step2",
63
+ disabled=True,
64
+ height=TEXT_AREA_HEIGHT)
65
+
66
+ st.header('STEP 3: Deduplicated by Similarity')
67
+ texts = [filter_pattern(t) for t in input_texts]
68
+ st.text_area("after process",
69
+ value=texts,
70
+ key="step3",
71
+ disabled=True,
72
+ height=TEXT_AREA_HEIGHT)
73
+
74
+ st.header('STEP 4: Deduplicated by Exact Matching')
75
+ texts = [filter_pattern(t) for t in input_texts]
76
+ st.text_area("after process",
77
+ value=texts,
78
+ key="step4",
79
+ disabled=True,
80
+ height=TEXT_AREA_HEIGHT)
81
+
82
+ st.header('STEP 5: Decontamination')
83
+ texts = [filter_pattern(t) for t in input_texts]
84
+ st.text_area("after process",
85
+ value=texts,
86
+ key="step5",
87
+ disabled=True,
88
+ height=TEXT_AREA_HEIGHT)
89
+
90
+ st.header('STEP 6: Anonymization')
91
+ texts = [filter_pattern(t) for t in input_texts]
92
+ st.text_area("after process",
93
+ value=texts,
94
+ key="step6",
95
+ disabled=True,
96
+ height=TEXT_AREA_HEIGHT)
97
+
98
+ st.header('Output')
99
+ st.text_area("output after cleansing",
100
+ value=sample_text,
101
+ key="output",
102
+ height=TEXT_AREA_HEIGHT)
requirements.txt ADDED
Binary file (2.41 kB). View file
 
step1.py ADDED
@@ -0,0 +1,917 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ #### Gamble Clean Words
4
+ GAMBLE_WORDS = [
5
+ "พนัน",
6
+ "แทงบอล",
7
+ "แทง",
8
+ "บาคารา",
9
+ "บา คา รา",
10
+ "เกมพนัน",
11
+ "คาสิโน",
12
+ "คา สิ โน",
13
+ "หวย",
14
+ "สล็อต",
15
+ "กาสิโน",
16
+ "casino",
17
+ "slot",
18
+ "เลขเด็ด",
19
+ "สูตรหวย",
20
+ "a s i n o",
21
+ "sbobet",
22
+ "fun88",
23
+ "ufabet",
24
+ "บาคาร่า",
25
+ "บา คา ร่า",
26
+ "รูเล็ต",
27
+ "ทำนายฝัน",
28
+ "เลขเด่น",
29
+ "สรุปผลบอล",
30
+ "ไฮไลท์ฟุตบอล",
31
+ "วิเคราะห์บอล",
32
+ "ดูบอลสด",
33
+ "พรีเมียร์ลีก",
34
+ "บอลประจำวัน",
35
+ "บอลเต็ง",
36
+ "บอลเด็ด",
37
+ "องค์ลงรวย",
38
+ "สูตรปลาตะเพียน",
39
+ "สามตัวตรง",
40
+ "วิเคราะห์ข้อมูลล่าง",
41
+ "ต่อ ครึ่งลูก",
42
+ "ครึ่งลูกลบ",
43
+ "เสมอควบครึ่ง",
44
+ "ครึ่งควบลูก",
45
+ ]
46
+
47
+ #### Sale Clean Words
48
+ SALE_SKIP_WORDS = [
49
+ "สอบราคา",
50
+ "จัดซื้อจัดจ้าง",
51
+ "ชมรม",
52
+ "สมาคม",
53
+ "นักลงทุน",
54
+ "นักการตลาด",
55
+ "ของกลาง",
56
+ "การลงทุน",
57
+ "นักวิเคราะห์",
58
+ "ขายให้แก่ประชาชน",
59
+ "การลดต้นทุน",
60
+ "การเสนอราคา",
61
+ "กระทรวง",
62
+ "ตลาดหลักทรัพย์",
63
+ "ยอดขายไม่ดี",
64
+ "ยอดขายไม่ค่อยดี",
65
+ "ผู้ประกอบการธุรกิจ",
66
+ "ออกใบอนุญาต",
67
+ "ผู้ประกอบกิจการ",
68
+ ]
69
+ SALE_URL_WORDS = [
70
+ "alibaba.com",
71
+ "shopee.co.th",
72
+ "lazada.com",
73
+ "DocPlayer.net",
74
+ "Alibaba",
75
+ "AliExpress",
76
+ "Aliexpress",
77
+ "TripAdvisor",
78
+ "jobbkk.com",
79
+ ]
80
+ SALE_WORDS = [
81
+ "ขาย",
82
+ "ซ่อม",
83
+ "ราคา",
84
+ "มือสอง",
85
+ "เช่า",
86
+ "ครีม",
87
+ "ฝ้ากระ",
88
+ "จุดด่างดำ",
89
+ "รับส่วนลด",
90
+ "โปรโมชั่น",
91
+ "กวดวิชา",
92
+ "ติวเตอร์",
93
+ "SEO",
94
+ "คอร์สเรียน SEO",
95
+ "จำหน่าย",
96
+ "ทัวร์",
97
+ "สินค้ามาใหม่",
98
+ "สินค้าทั้งหมด",
99
+ "รีวิวสินค้า",
100
+ "เคสกันกระแทก",
101
+ "ประกาศ",
102
+ "ลงขายของ",
103
+ "เลือกขนาด",
104
+ "บริการจัดส่ง",
105
+ "จัดอันดับ",
106
+ "คาราโอเกะ",
107
+ "จำหน่าย",
108
+ "หาเงินออนไลน์",
109
+ "สั่งซื้อ",
110
+ "ลดกระหนำ่",
111
+ "รหัส",
112
+ "ลงประกาศฟรี",
113
+ "หยิบใส่ตะกร้า",
114
+ "สนใจ",
115
+ "ซื้อ",
116
+ "สินค้า",
117
+ "ผลิตภัณฑ์",
118
+ ]
119
+
120
+ #### Rent Clean Words
121
+ RENT_SKIP_WORDS = [
122
+ "สอบราคา",
123
+ "จัดซื้อจัดจ้าง",
124
+ "ชมรม",
125
+ "สมาคม",
126
+ "นักลงทุน",
127
+ "นักการตลาด",
128
+ "ของกลาง",
129
+ "การลงทุน",
130
+ "นักวิเคราะห์",
131
+ "ขายให้แก่ประชาชน",
132
+ "การลดต้นทุน",
133
+ "การเสนอราคา",
134
+ "กระทรวง",
135
+ "ตลาดหลักทรัพย์",
136
+ ]
137
+ RENT_WORDS = [
138
+ "บ้านมือสอง",
139
+ "ให้เช่า",
140
+ "เช่า",
141
+ "บ้านเดี่ยว",
142
+ "อพาร์ทเม้นท์",
143
+ "อสังหาริมทรัพย์",
144
+ "เพนท์เฮ้าส์",
145
+ "ทาวน์เฮ้าส์",
146
+ ]
147
+
148
+ #### Script Clean Words
149
+ SCRIPT_WORDS = [
150
+ "function",
151
+ "var",
152
+ "click",
153
+ "margin",
154
+ "width",
155
+ "height",
156
+ "return",
157
+ "else",
158
+ "alert",
159
+ "<br>",
160
+ "href",
161
+ ]
162
+
163
+ #### Garbage Clean Words
164
+ GARBAGE_WORDS = [
165
+ "โหงวเฮ้ง",
166
+ "ครีมฟอกสี",
167
+ "ครีมผิวขาว",
168
+ "ฟอกสี",
169
+ "ไวท์เทนนิ่งครีม",
170
+ "ครีมไวท์เทนนิ่ง",
171
+ "ครีมลบฝ้ากระ",
172
+ "รับสร้างบ้าน",
173
+ "ครีมโรคสะเก็ดเงิน",
174
+ "บร��การจองตั๋ว",
175
+ "บริการรีดผ้า",
176
+ "อาหารเสริมลดน้ำหนัก",
177
+ "ยาลดน้ำหนัก",
178
+ "ลดไขมัน",
179
+ "ผิงโซดา",
180
+ "สร้างบ้าน",
181
+ "ช่างกุญแจ",
182
+ "ช่างโลหะ",
183
+ "ช่างโยธา",
184
+ "ช่างเครื่องยนต์",
185
+ "ช่างไม้",
186
+ "ช่างกลโรงงาน",
187
+ "ช่างไฟฟ้า",
188
+ "ปรสิต",
189
+ "หนอน",
190
+ "เวิร์ม",
191
+ ]
192
+
193
+ #### Football teams
194
+ FOOTBALL_TEAMS = [
195
+ "ยูเวนตุส",
196
+ "อินเตอร์ มิลาน",
197
+ "นาโปลี",
198
+ "เอซี มิลาน",
199
+ "ลาซิโอ",
200
+ "โรม่า",
201
+ "กัลโซ่",
202
+ "เซเรีย",
203
+ "ปาร์ม่า",
204
+ "เอฟเวอร์ตัน",
205
+ "ซันเดอร์แลนด์",
206
+ "ลิเวอร์พูล",
207
+ "แมนเชสเตอร์",
208
+ "นิวคาสเซิล",
209
+ "เชลซี",
210
+ "อาร์เซนอล",
211
+ "คลิสตัลพาเลช",
212
+ "เซาแทมป์ตัน",
213
+ "เซาแธมป์ตัน",
214
+ "เชฟฟิลด์",
215
+ "ฟอเรสต์",
216
+ "เบอร์ตัน",
217
+ "เบรนท์ฟอร์ด",
218
+ "ฟูแล่ม",
219
+ "ไฮไลท์ฟุตบอล",
220
+ "เลบันเต้",
221
+ "บาร์เซโลน่า",
222
+ "เรอัล มาดริด",
223
+ "เอสปันญ่อล",
224
+ ]
225
+
226
+ #### Hotels Advertising
227
+ HOTEL_AD = [
228
+ "โรงแรมอันดับ",
229
+ "ที่พักแบบพิเศษอันดับ",
230
+ "สถานที่พักอันดับ",
231
+ "สถานที่พักคุ้มค่าอันดับ",
232
+ "โรงแรมใกล้กับ",
233
+ "โรงแรมที่ใกล้",
234
+ "โรงแรม 4 ดาว",
235
+ "โรงแรม 3 ดาว",
236
+ "ที่พักพร้อมอาหารเช้า",
237
+ "โรงแรมราคาถูก",
238
+ "โรงแรมหรู",
239
+ ]
240
+
241
+ #########
242
+ # PRE-COMPILE REGEX to object for speed up processing.
243
+ #########
244
+ # -----------------------------------------------------
245
+ # Remove useless row that make overhead in regex processing
246
+
247
+ # Unusual row - line size too large
248
+ # if there are 3 large lines ( 500 characters each)
249
+ TOOLARGE_LINE_PATTERN = ".{1500}"
250
+ TOOLARGE_RE = re.compile(TOOLARGE_LINE_PATTERN, re.MULTILINE)
251
+
252
+ NONECHAR_PATTERN = "๮|๞|๨|๡|๷|๻|๫|͹"
253
+ NONECHAR_RE = re.compile(NONECHAR_PATTERN, re.MULTILINE)
254
+
255
+ NONE_TONE_MARK_PATTERN = "ก าหนด|เป าหมาย|พ ฒนา|ค ณภาพ|ว จ ย|ค ณล กษณะ|ต างๆ|เป น |ให |บร หาร|ปร บปร ง|ใหม|อย าง|เง น"
256
+ NONE_TONE_MARK_RE = re.compile(NONE_TONE_MARK_PATTERN, re.MULTILINE)
257
+
258
+ # -----------------------------------------------------
259
+
260
+ GAMBLE_PATTERN = "|".join(GAMBLE_WORDS)
261
+ GAMBLE_RE = re.compile(GAMBLE_PATTERN, re.MULTILINE)
262
+
263
+ FOOTBALL_PATTERN = "|".join(FOOTBALL_TEAMS)
264
+ FOOTBALL_RE = re.compile(FOOTBALL_PATTERN, re.MULTILINE)
265
+
266
+ HOTEL_AD_PATTERN = "|".join(HOTEL_AD)
267
+ HOTEL_AD_RE = re.compile(HOTEL_AD_PATTERN, re.MULTILINE)
268
+
269
+ SALE_URL_PATTERN = "|".join(SALE_URL_WORDS)
270
+ SALE_URL_RE = re.compile(SALE_URL_PATTERN, re.MULTILINE)
271
+ SALE_SKIP_PATTERN = "|".join(SALE_SKIP_WORDS)
272
+ SALE_SKIP_RE = re.compile(SALE_SKIP_PATTERN, re.MULTILINE)
273
+ SALE_PATTERN = "|".join(SALE_WORDS)
274
+ SALE_RE = re.compile(SALE_PATTERN, re.MULTILINE)
275
+
276
+ RENT_SKIP_PATTERN = "|".join(RENT_SKIP_WORDS)
277
+ RENT_SKIP_RE = re.compile(RENT_SKIP_PATTERN, re.MULTILINE)
278
+ RENT_PATTERN = "|".join(RENT_WORDS)
279
+ RENT_RE = re.compile(RENT_PATTERN, re.MULTILINE)
280
+
281
+ JSON_PATTERN = r"\s*\"(?:\w)*\"\s*:"
282
+ JSON_RE = re.compile(JSON_PATTERN, re.MULTILINE)
283
+
284
+ SCRIPT_PATTERN = r"\b" + "|".join(SCRIPT_WORDS) + r"\b"
285
+ SCRIPT_RE = re.compile(SCRIPT_PATTERN, re.MULTILINE)
286
+
287
+ GARBAGE_PATTERN = "|".join(GARBAGE_WORDS)
288
+ GARBAGE_RE = re.compile(GARBAGE_PATTERN, re.MULTILINE)
289
+
290
+ GHOST_PATTERN = "เธฃเน|เธเธญ|เธเน|เธฐเธ|เธฅเธฐ|เธซเธฒ|เธญเธฒ|เธดเธ|เธตเธข|เธญเน|เธญเธ|เธดเน|เธฑเธ|เธกเน|เธฒเธ|เธชเน|เน€เธ"
291
+ GHOST_RE = re.compile(GHOST_PATTERN, re.MULTILINE)
292
+
293
+ HEX_PATTERN = "(?<![^ ])(?:[0-9A-Fa-f]{2})(?![^ ])"
294
+ HEX_RE = re.compile(HEX_PATTERN, re.MULTILINE)
295
+
296
+ PAGE_PATTERN = "(?:<<[ ])?(?:ก่อนหน้า|ย้อนกลับ)[ ]{0,2}(?:\[[ ]?\d{0,6}[ ]?\]|[ ]?\d{0,6}[ ]?)*(?:ต่อไป|หน้าถัดไป|ถัดไป)?(?:[ ]?>>)?|<<(?:[ ]\d{0,6}[ ]\-[ ]\d{0,6})+[ ].{0,100}"
297
+ PAGE_RE = re.compile(PAGE_PATTERN, re.MULTILINE)
298
+
299
+ EMBEDDED_SERVER_PATTERN = "<%[ ]*[^%]*%>|<%.*"
300
+ EMBEDDED_SERVER_RE = re.compile(EMBEDDED_SERVER_PATTERN, re.MULTILINE)
301
+
302
+ U_PATTERN = "\uFEFF|\u00AD|[\u200A-\u200F]|\uFFFD|[\uE000-\uF8FF]|[\u202A-\u202C]|\u0092|[\u0091-\u0096]|\u2028|\u2066|\u2069|\u008d|\u0081|\u008E|<U\+[0-9A-Fa-f]{4}>"
303
+ U_RE = re.compile(U_PATTERN, re.MULTILINE)
304
+
305
+ BLOCK_PATTERN = "(?:\[[^\]]*\])|(?:«[^»]*»)|(?:<<([^>]*)>>)"
306
+ BLOCK_RE = re.compile(BLOCK_PATTERN, re.MULTILINE)
307
+
308
+ EMAIL_PATTERN = "(?:(?:([Ee]?mail|อีเมล์)[ ]{0,2}:?[ ]{0,5})?)[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
309
+ EMAIL_RE = re.compile(EMAIL_PATTERN, re.MULTILINE)
310
+
311
+ URL_PATTERN = r"\b(?:(?:https?|ftp)://[^\s/$\.\?#].[^\s]*)\b|\b(?:www\.?)?(?:(?:[\w-]*)\.)*(?:com|net|org|info|biz|me|io|co|asia|xyz|th|cn|in|uk|jp|ru)\b"
312
+ URL_RE = re.compile(URL_PATTERN, re.MULTILINE)
313
+
314
+ MENU1_PATTERN = "\|(?:[^\|\n]*\|)+.*"
315
+ MENU1_RE = re.compile(MENU1_PATTERN, re.MULTILINE)
316
+
317
+ MENU2_PATTERN = "\|(?:[^\|\n]*\|)+"
318
+ MENU2_RE = re.compile(MENU2_PATTERN, re.MULTILINE)
319
+
320
+ MENU3_PATTERN = "(?:(?:[^/\n]*/){4,}.*)"
321
+ MENU3_RE = re.compile(MENU3_PATTERN, re.MULTILINE)
322
+
323
+ MENU4_PATTERN = "^[^\n]{0,20}[ ]{0,2}[>»\\\\].*"
324
+ MENU4_RE = re.compile(MENU4_PATTERN, re.MULTILINE)
325
+
326
+ HASHTAG_PATTERN = "#\d*[ ].{0,300}|#(?:(?:[^ \n]*)[ ]?)+|Tag Archives[ ]{0,2}:.{0,300}|Posts Tagged[ ]{0,2}:.{0,300}|HASTAG[ ]{0,2}:.{0,300}|Tag[s]?[ ]{0,2}:.{0,300}|Tagged[ ].{0,300}"
327
+ HASHTAG_RE = re.compile(HASHTAG_PATTERN, re.MULTILINE)
328
+
329
+ SIDEBAR_PATTERN = ".{0,40}(?:(?:\[|\()\d{0,9}(?:\]|\))(?:[ ]{0,2})?,?)"
330
+ SIDEBAR_RE = re.compile(SIDEBAR_PATTERN, re.MULTILINE)
331
+
332
+ MARKUP_PATTERN = "\{\{[^\}]*\}\}|\{\{.*"
333
+ MARKUP_RE = re.compile(MARKUP_PATTERN, re.MULTILINE)
334
+
335
+ IFRAME_PATTERN = "<iframe.*?<\/iframe>\s*|<iframe.*"
336
+ IFRAME_RE = re.compile(IFRAME_PATTERN, re.MULTILINE)
337
+
338
+ IP_PATTERN = "\((?:(?:X{1,3}|\d{1,3})\.){3}(?:X{1,3}|\d{1,3})\)|\(?IP:?[ ]?(?:(?:X{1,3}|\d{1,3})\.){3}(?:X{1,3}|\d{1,3})\)?"
339
+ IP_RE = re.compile(IP_PATTERN, re.MULTILINE)
340
+
341
+ TEL_PATTERN = "(?:(?:[Pp]hone|[Mm]obile|มือถือ|Tel|TEL|Fax|FAX|เบอร์โทรศัพท์|เลขโทรศัพท์|เบอร์ติดต่อ|โทรศัพท์|โทรสาร[ ]{0,2}:|เบอร์โทร|โทร[ ]{0,2}:|โทร\.|โทร[ ]|ติดต่อที่[ ]{0,2}:?|ติดต่อ[ ]{0,2}:?)[ ]{0,2}):?(?:(?:[ ]{0,2})?(?:(?:\d{3}-\d{7})|(?:\d{4}-\d{6})|(?:\d{3}-\d{3}-\d{4}|(?:\d{3}-\d{3}-\d{3})|(?:\d{1}-\d{4}-\d{4})|(?:\d{2}-\d{3}-\d{4})|(?:\d{2}\s\d{3}\s\d{4})|(?:\d{2}-\d{7})|(?:\d{3}\s\d{3}\s\d{4})|(?:\d{3}\s\d{3}\s\d{3})|(?:\d{10})))[ ]{0,2},?)+|02\d{7}|0[3-7][2-9]\d{6}|0[6-9][0-9]\d{7}"
342
+ TEL_RE = re.compile(TEL_PATTERN, re.MULTILINE)
343
+
344
+ DATE1_PATTERN = "(?:(?:การปรับปรุงปัจจุบัน|ตั้งแต่|ลงประกาศเมื่อ|อัพเดทล่าสุด|แก้ไขครั้งสุดท้าย|แก้ไขครั้งล่าสุด|เผยแพร่เมื่อ|เผยแพร่|เขียนเมื่อ|ตอบเมื่อ|เมื่อ|เขียนวันที่|วันที่|วัน)?(?:[ ]{0,2}:[ ]{0,2})?(?:จันทร์|อังคาร|พุธ|พฤหัสบดี|พฤหัสฯ?\.?|ศุกร์|เสาร์|อาทิตย์|จ\.|อ\.|พ\.|พฤ\.|ศ\.|ส\.|อา\.?)?(?:[ ]{0,2}ที่)?(?:[ ]{0,2}[\w\u0E01-\u0E5B]*[ ]{0,2}(?:,|(?:-|\u2013)))?(?:\d{1,4}[ ]{0,2}-)?[ ]{0,2}\d{0,4}(?:-|[ ]{0,2})(?:เดือน[ ]{0,2})?(?:มกราคม|กุมภาพันธ์|มีนาคม|เมษายน|พฤษภาคม|มิถุนายน|กรกฎาคม|สิงหาคม|กันยายน|ตุลาคม|พฤศจิกายน|ธันวาคม| ม\.?ค\.? | ก\.?พ\.? | มี\.?ค\.? | เม\.?ย\.? | พ\.?ค\.? | มิ\.?ย\.? | ก\.?ค\.? | ส\.?ค\.? | ก\.?ย\.? | ต\.?ค\.? | พ\.?ย\.? | ธ\.?ค\.? |January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec|/)(?:-|[ ]|,]){0,2}(?:[ ]{0,2})?(?:\d{4}|\d{2})?,?(?:(?:[ ]{0,2}\d{4}|\d{2}:\d{2}:\d{2})|(?:[ ]{0,2}\d{1,2}:\d{2}(?::\d{2})?[ ]*(?:(?:(?:p|P|a|A)(?:m|M)))?)|[ ]{0,2}เวลา[ ]{0,2}\d{1,2}:\d{2}:\d{2})?(?:[ ]{0,2}น\.)?(?:[ ]{0,2}:[ ]{0,2}\d{1,2}(?:\.|:)\d{2}[ ]{0,2}น\.)?(?:[ ]{0,2}เวลา[ ]{0,2}\d{1,2}:\d{2}[ ]{0,2}[pPaA][mM][ ]{0,2}(?:PDT)?)?(?:[ ]{0,2}เวลา[ ]{0,2}\d{1,2}(?:\.|:)\d{2}[ ]{0,2}(?:น\.)?)?(?:[ ]*\d*[ ]{0,9}ผู้ชม[ ]{0,2}\d)?(?:[ ]{0,2}เวลา[ ]{0,2}:[ ]{0,2}\d{1,2}:\d{2}:\d{2}[ ](?:น\.)?)?(?:[ ]{0,2}เข้าชม[ ]{0,2}\d*[ ]{0,8}ครั้ง)?(?:[ ]{0,2}ที่[ ]{0,2}\d{1,2}:\d{2}[ ]{0,2}(?:[pPaA][mM])?)?(?:[ ]{0,2}Views:[ ]{0,2}(?:\d{0,3},?){0,3})?(?:[ ]{0,2}\(\d{1,2}:\d{2}[ ](?:น\.)?[ ]{0,2}\)(?:[ ]{0,2}ความคิดเห็น[ ]{0,2}\d)?)?(?:[ ]{0,2}-[ ]{0,2}\d{1,2}:\d{2}[ ]{0,2}(?:น\.)?)?(?:[ ]{0,2}จำนวนเข้าชม:(?:[ ]{0,2}\d{0,9},?)*)?(?:[ ]*พ\.ศ\.[ ]{0,2}\d{4}[ ]{0,2}(?:\d{1,2}:\d{2}[ ]{0,2}(?:น\.)?)?)?(?:(?:\d{0,3},?){0,3}[ ]{0,2}ครั้ง)?(?:[ ]{0,2}-\d{1,2}(?:\.|:)?\d{2}[ ]{0,2}(?:น\.)?)?(?:เวลา[ ]{0,2}\d{1,2}:\d{2}:\d{2})?(?:[ ]{0,2}(?:ถึง|จนถึง))?)(?:,[ ]{0,2})?(?:[ ]{0,2}(?:[pPaA][mM])?)?|(?:(?:[ ]{0,2}(?:\d{4}|\d{2})-\d{1,2}-(?:\d{4}|\d{1,2}))(?:[ ]{0,2},[ ]{0,2})?(?:\d{1,2}(?:\.|:)\d{2}[ ]{0,2}(?:#\d*)?(?:น\.)?)?(?:[ ]{0,2}\d{1,2}:\d{2}:\d{2})?)|(?:(?:เปิดบริการ[ ]{0,2})?(?:เวลา[ ]{0,2}\d{1,2}:\d{2}-\d{1,2}:\d{2}[ ]{0,2}(?:น\.)?))|(?:(?:Time[ ]Online[ ]:[ ]{0,2})?(?:\d{1,2}:\d{2}:\d{2})(?:[ ]{0,2}น\.)?(?:[ ]{0,2}[pPaA][mM])?)|(?:\(\d{1,2}:\d{2}[ ]{0,2}-[ ]{0,2}\d{1,2}:\d{2}(?:[ ]*น\.)?(?:[ ]{0,2}[pPaA][mM])?\))|(?:นี้[ ]{0,2}เวลา[ ]{0,2}\d{1,2}(?:\.|:)\d{2}[ ]{0,2}(?:น\.)?)|(?:\d{1,2}[ ]{0,2}(?:มกราคม|กุมภาพันธ์|มีนาคม|เมษายน|พฤษภาคม|มิถุนายน|กรกฎาคม|สิงหาคม|กันยายน|ตุลาคม|พฤศจิกายน|ธันวาคม))"
345
+ DATE1_RE = re.compile(DATE1_PATTERN, re.MULTILINE)
346
+ DATE2_PATTERN = "[พค]\.?ศ\.?[ ]{0,2}\d{4}|\d{4}[ ]{0,2}เวลา[ ]{0,2}\d{2}:?\.?\d{2}(?:[ ][Pp][Mm])|[พค]\.?ศ\."
347
+ DATE2_RE = re.compile(DATE2_PATTERN, re.MULTILINE)
348
+
349
+ HTML_PATTERN = (
350
+ "<br>?|&nbsp|{\s*document\..*|SELECT.*FROM.*WHERE.*|<a\s*href=.*|<img\s*src=.*"
351
+ )
352
+ HTML_RE = re.compile(HTML_PATTERN, re.MULTILINE)
353
+
354
+ REFINE1_PATTERN = "^[ ]?ตอนที่[ ]*\d{0,3}(?:[-–]?\d{0,3})?[ ]{0,2}.{0,100}|^สั่ง.{0,50}บาท|^[ ]?เลขจดแจ้ง[ ]{0,2}.{0,13}|^.{0,100}\.jpg[ ]{0,2}\(.{0,50}|^.{0,20}รายการ|^[ ]?สนใจ[ ]{0,2}.{0,15}โทร[ ]{0,2}.{0,12}|^[ ]?ผู้แสดงความคิดเห็น.{0,60}|^\(.{0,40}[ ]{0,2}\d{0,5}[ ]{0,2}.{0,10}\).{0,200}|^[ ]?ผู้เข้าชมทั้งหมด.{0,30}|^[ ]?ฉบับที่[ ]{0,2}\d{0,7}[^-–]{0,30}-?–?[ ]|^[ ]?โพสต์ที่แชร์โดย.{0,200}|^[ ]?Copyright.{0,200}|กำลังแสดงหน้าที.{0,200}|[ ]{0,2}รีวิว.{0,100}|^[ ]?ข้อที่ \d{0,4}|^เข้าชม/ผู้ติดตาม.{0,13}"
355
+ REFINE1_RE = re.compile(REFINE1_PATTERN, re.MULTILINE)
356
+
357
+ REFINE2_PATTERN = "Submitted[ ]by.{0,100}|^เขียนโดย.{0,100}|^Poste?d?[ ]{0,2}(?:by|on){0,2}.{0,100}|^เมื่อวาน[ ]{0,2}\d.{0,100}|^อาทิตย์นี้[ ]{0,2}\d.{0,100}|^อาทิตย์ที่แล้ว[ ]{0,2}\d.{0,100}|^เดือนนี้[ ]{0,2}\d.{0,100}|^เดือนที่แล้ว[ ]{0,2}\d.{0,100}|^รวมผู้เยี่ยมชม[ ]{0,2}\d.{0,100}|^จำนวนผู้ชมโดยประมาณ[ ]{0,2}\d.{0,100}|^รหัสสินค้า[ ]{0,2}\d.{0,100}|^บาร์โค้ด[ ]{0,2}\d.{0,100}|^[ ]โดย[ ]{0,2}.{0,100}|^เข้าชม[ ]{0,2}\d.{0,100}|^โหวต[ ]{0,2}\d.{0,100}|^มุมมอง[ ]{0,2}\d.{0,100}"
358
+ REFINE2_RE = re.compile(REFINE2_PATTERN, re.MULTILINE)
359
+
360
+ REFINE3_PATTERN = "^[^@\n]{0,30}@\d{0,10}.{0,30}|.{0,100}[-]$|\d*[ ]*x[ ]\d*[^ ][ ]?|^ดูหนัง[ ]?(?:ออนไลน์)?[ ].{0,60}|^คุ้มค่าที่สุดอันดับ[ ]{0,2}\d{0,2}.{0,80}|^เปิด[^\d\n]+.{0,10}"
361
+ REFINE3_RE = re.compile(REFINE3_PATTERN, re.MULTILINE)
362
+
363
+ REFINE4_PATTERN = "^[^\n]{0,50}คลิก\)|[Ff]acebook[ ]{0,2}(?:\d{0,3},?\d{0,3})[ ]{0,2}เข้าชม|^[ ]{0,2}[^ ]{0,20}[ ]{0,2}\d{0,9}[ ]{0,2}ความเห็น|\[url=.{0,100}|^ผู้ชม[ ]{0,2}(?:\d{0,3},?)+|\([ ]?\)"
364
+ REFINE4_RE = re.compile(REFINE4_PATTERN, re.MULTILINE)
365
+
366
+ REFINE5_PATTERN = "^[^\d]{0,30}\d{0,10}[ ]{0,2}views.{0,100}|^Prev.{0,100}Next|^สินค้าติดต่อที่.{0,100}|^อ่านต่อคลิก.{0,100}|^สินค้าโปรโมชั่น.{0,200}|^US[ ]?\$\d{0,3},?\d{0,3}.?\d{0,3}.{0,50}"
367
+ REFINE5_RE = re.compile(REFINE5_PATTERN, re.MULTILINE)
368
+
369
+ REFINE6_PATTERN = "^เจ้าหน้าที่ฝ่ายขาย:\n.{0,80}|^(?:\*+[ ]{0,2}[^\*\n]{0,50}[ ]{0,2}\*+)[ ]{0,2}[\+]?(?:(?:\d{0,3},?)+)?|[\*\+]{2,5}|^(?:[^:\n]{0,30}:).{0,200}"
370
+ REFINE6_RE = re.compile(REFINE6_PATTERN, re.MULTILINE)
371
+
372
+ REFINE7_PATTERN = "\(?อ่าน[ ]{0,2}\d{0,3},?\d{0,3}[ ]{0,2}(?:ครั้ง[ ]{0,2})?\)?|โพสต์[ ].{0,100}|Read[ ]{0,2}\d{0,9}[ ]{0,2}times|[^ \n]{0,20}[ ]{0,2}pantip|^Previous (?:Post|article).{0,150}|^Next (?:Post|article).{0,150}|^ตอบกลับ[ ]{0,2}.{0,200}"
373
+ REFINE7_RE = re.compile(REFINE7_PATTERN, re.MULTILINE)
374
+
375
+ REFINE8_PATTERN = "^[ ]?(?:[Pp]ostby|[Pp]osted[ ](?:by|on)).*|^[ ]?เข้าชม/ผู้ติดตาม.*|^[ ]?จำนวนผู้ชมโดยประมาณ[ :]?.*|^[ ]?ลงประกาศฟรี[ ].*|^\|[ ]|^[ ]?จาก[ ].*|^[ ]?By.*|^[ ]{0,2}?โดย[ ]{0,2}?.*"
376
+ REFINE8_RE = re.compile(REFINE8_PATTERN, re.MULTILINE)
377
+
378
+ REFINE9_PATTERN = "^[^\n\.]{0,60}\.{3}$|^[^\n]{0,30}ฉบับที่[ ].*|^Home[ ]/[ ].{100}|^[^\n\|]{0,60}\|.{0,60}"
379
+ REFINE9_RE = re.compile(REFINE9_PATTERN, re.MULTILINE)
380
+
381
+ REFINE10_PATTERN = "^[ ]?(?:\)|↑|►|←|«)[ ]?|^[-_]+"
382
+ REFINE10_RE = re.compile(REFINE10_PATTERN, re.MULTILINE)
383
+
384
+ REFINE11_PATTERN = "^สถิติ(?:วันนี้|สัปดาห์นี้|เมื่อวาน(?:นี้)?|เดือนนี้)[ ]{0,2}.{0,50}|Online[ ]สถิติ.{0,50}"
385
+ REFINE11_RE = re.compile(REFINE11_PATTERN, re.MULTILINE)
386
+
387
+ REFINE12_PATTERN = (
388
+ "^[^\n\(]{0,80}\(รายละเอียด\)[ ]\(แจ้งลิงก์เสีย\)|^ด[\. ][ชญ][\. ].*|\.{5,}"
389
+ )
390
+ REFINE12_RE = re.compile(REFINE12_PATTERN, re.MULTILINE)
391
+
392
+ REFINE13_PATTERN = "^[ ]?(?:เรื่องย่อ[ ].{0,100}|คุ้มค่าที่สุดอันดับ.{0,100}|คุ้[ ]่าที่สุดอันดับ.{0,100}|\(?ลงโฆษณาฟรี[ ].{0,200}|\(free[ ]online[ ].{0,100}|\(คลิกเพื่อดูต้นฉบับ\)[ ].{0,100}|แก้ไขครั้งสุดท้ายโดย[ ].{0,100})|^[^\d\n]{0,30}[ ]\d{0,3},?\d{0,3}[ ]ครั้ง.{0,50}"
393
+ REFINE13_RE = re.compile(REFINE13_PATTERN, re.MULTILINE)
394
+
395
+ REFINE14_PATTERN = "^(?:[฿$]?\d{0,9}\.?,?\d{0,9}-?–?:?/?(?:[ ]{0,2}x[ ]{0,2}\d{0,8})?(?:\\bกม\\b\.?)?(?:\\bน\\b\.)?(?:ล้าน|แสน|หมื่น|พัน|ร้อย|สิบ|บาท|[ ])?){0,5}"
396
+ REFINE14_RE = re.compile(REFINE14_PATTERN, re.MULTILINE)
397
+
398
+ from datetime import datetime
399
+ from typing import List, Dict
400
+ import re
401
+
402
+
403
+ def clean_with_remove_document(text: str) -> bool:
404
+ # ---- Clean too large unused lines
405
+ # Limit matches list to 2 items only, enough
406
+ matches = TOOLARGE_RE.findall(text)[:2]
407
+ # Classify as toolarge row if number of matches = 2
408
+ if len(matches) == 2:
409
+ return True
410
+
411
+ # ---- Clean none characters row
412
+ # Limit matches list to 25 items
413
+ matches = NONECHAR_RE.findall(text)[:25]
414
+ # Classify as none character row if number of matches = 25
415
+ if len(matches) == 25:
416
+ return True
417
+
418
+ # ---- Clean none tone mark row
419
+ # Limit matches list to 25 items
420
+ matches = NONE_TONE_MARK_RE.findall(text)[:25]
421
+ # Classify as none tone mark row if number of matches = 25
422
+ if len(matches) == 25:
423
+ return True
424
+
425
+ # ---- Clean Gamble ~ 9.2% of mC4 data
426
+ # if found gamble word 2 times in a row, classify as gamble row
427
+ # remove the row
428
+ # Limit matches list to 2 items only, enough
429
+ matches = GAMBLE_RE.findall(text)[:2]
430
+ # Classify as gamble if number of matches = 2
431
+ if len(matches) == 2:
432
+ return True
433
+
434
+ # ---- Clean Football data
435
+ # if found gamble word 4 times in a row, classify as football data
436
+ # remove the row
437
+ # Limit matches list to 4 items only
438
+ matches = FOOTBALL_RE.findall(text)[:4]
439
+ if len(matches) == 4:
440
+ return True
441
+
442
+ # ---- Clean Hotel Advertising
443
+ # if found hotel word 4 times in a row, classify as Hotel Ad. data
444
+ # remove the row
445
+ # Limit matches list to 4 items only, enough
446
+ matches = HOTEL_AD_RE.findall(text)[:4]
447
+ if len(matches) == 4:
448
+ return True
449
+
450
+ # ---- Clean Sale ~26% of mC4 data
451
+ # Sale row data is diverse,
452
+ # so the regex is not used in this case.
453
+ # Rules:
454
+ # 1. Remove row if it contains common specific Sale's URL
455
+ # 2. Skip to next clean rule if it contains specific keywords, eg. "สอบราคา", "จัดซื้อจัดจ้าง, etc."
456
+ # 3. If not found keywords in (2) then scan the row with sale keywords, if there are at leat 3 sale kewords found then remove the row.
457
+
458
+ if SALE_URL_RE.search(text):
459
+ return True
460
+
461
+ if not SALE_SKIP_RE.search(text):
462
+ # Classify as Sale data ( 3 matches, can be adjusted)
463
+ matches = SALE_RE.findall(text)[:3]
464
+ if len(matches) == 3:
465
+ return True
466
+
467
+ # ---- Clean Rent (พวกเช่า ~2% of mC4 data)
468
+ # Rent use another rules
469
+ # 1. find skip words in the row. If found, skip to next rule (not remove)
470
+ # 2. if found rent word 2 times in a row, classify as rent row
471
+ # remove the row
472
+
473
+ if not RENT_SKIP_RE.search(text):
474
+ # Limit matches list to 2 items only, enough
475
+ matches = RENT_RE.findall(text)[:2]
476
+ if len(matches) == 2:
477
+ return True
478
+
479
+ # ---- Clean pattern (json like -> "abc": ~.5-1% )
480
+ # 99% can classify as gabage: so remove them
481
+ # match n items to make sure they are garbages n=20, can change
482
+ matches = JSON_RE.findall(text)[:20]
483
+ # if match only 20+, classify as garbage
484
+ if len(matches) == 20:
485
+ return True
486
+
487
+ # ---- Clean script (Javascript, etc. ~.5% )
488
+ # 99% can classify as gabage: so remove them
489
+ matches = SCRIPT_RE.findall(text)[:10]
490
+ # Classify as script if number of matches = 10
491
+ if len(matches) == 10:
492
+ return True
493
+
494
+ # ---- Clean garbage (useless or not necessary ~.45%)
495
+ # classify as gabage: so remove them
496
+ matches = GARBAGE_RE.findall(text)[:4]
497
+ # Classify as garbage if number of matches = 4
498
+ if len(matches) == 4:
499
+ return True
500
+
501
+ # ---- Clean ghost language (~0.008% can cancel this clean)
502
+ # classify as ghost : so remove them
503
+ matches = GHOST_RE.findall(text)[:4]
504
+ # Classify as ghost if number of matches = 4
505
+ if len(matches) == 4:
506
+ return True
507
+
508
+ # ---- Clean HEX code
509
+ # classify as HEX : so remove them
510
+ matches = HEX_RE.findall(text)[:25]
511
+ # Classify as HEX if number of matches = 25
512
+ if len(matches) == 25:
513
+ return True
514
+
515
+ return False
516
+
517
+
518
+ def clean_mc4_text(text: str) -> str:
519
+ text = PAGE_RE.sub(" ", text)
520
+ text = EMBEDDED_SERVER_RE.sub(" ", text)
521
+ text = U_RE.sub(" ", text)
522
+ text = EMAIL_RE.sub(" ", text)
523
+ text = URL_RE.sub(" ", text)
524
+ text = MENU1_RE.sub(" ", text)
525
+ text = MENU2_RE.sub(" ", text)
526
+ text = MENU3_RE.sub(" ", text)
527
+ text = MENU4_RE.sub(" ", text)
528
+ text = SIDEBAR_RE.sub(" ", text)
529
+ text = BLOCK_RE.sub(" ", text)
530
+ text = HASHTAG_RE.sub(" ", text)
531
+ text = MARKUP_RE.sub(" ", text)
532
+ text = IFRAME_RE.sub(" ", text)
533
+ text = IP_RE.sub(" ", text)
534
+ text = TEL_RE.sub(" ", text)
535
+ text = DATE1_RE.sub(" ", text)
536
+ text = DATE2_RE.sub(" ", text)
537
+ text = HTML_RE.sub(" ", text)
538
+
539
+ # --- Refinements (in sequence)
540
+ text = REFINE1_RE.sub(" ", text)
541
+ text = REFINE2_RE.sub(" ", text)
542
+ text = REFINE3_RE.sub(" ", text)
543
+ text = REFINE4_RE.sub(" ", text)
544
+ text = REFINE5_RE.sub(" ", text)
545
+ text = REFINE6_RE.sub(" ", text)
546
+ text = REFINE7_RE.sub(" ", text)
547
+ text = REFINE8_RE.sub(" ", text)
548
+ text = REFINE9_RE.sub(" ", text)
549
+ text = REFINE10_RE.sub(" ", text)
550
+ text = REFINE11_RE.sub(" ", text)
551
+ text = REFINE12_RE.sub(" ", text)
552
+ text = REFINE13_RE.sub(" ", text)
553
+ text = REFINE14_RE.sub(" ", text)
554
+
555
+ # Split the text into lines and remove any empty lines
556
+ lines = [line for line in text.split("\n") if line]
557
+
558
+ # Initialize the list with the first line
559
+ deduplicated_list = [lines[0]]
560
+
561
+ # Iterate over the rest of the lines
562
+ for i in range(1, len(lines)):
563
+ # Find the common prefix between this line and the previous line
564
+ common_prefix = ""
565
+ for char1, char2 in zip(lines[i], lines[i - 1]):
566
+ if char1 == char2:
567
+ common_prefix += char1
568
+ else:
569
+ break
570
+
571
+ # Remove the common prefix from this line and add it to the list
572
+ deduplicated_list.append(lines[i][len(common_prefix) :])
573
+
574
+ text = "\n".join(deduplicated_list)
575
+
576
+ # Clean short lines
577
+ # ( len(line) <= 30 characters , cut this line off)
578
+ text = "\n".join(line for line in text.split("\n") if len(line) > 30)
579
+
580
+ # ---- The scan row that passes all filter is written to disk
581
+ # before write to disk, get rid of spaces by change them to single space (' ').
582
+
583
+ text = re.sub("[ ]+", " ", text, 0, re.MULTILINE)
584
+ text = re.sub("^[ ]", "", text, 0, re.MULTILINE)
585
+ text = re.sub(r"\n\s*", "\n", text, 0, re.MULTILINE)
586
+
587
+ return text
588
+
589
+
590
+ def clean_dataset(dataset: List[Dict[str, str]]) -> List[Dict[str, str]]:
591
+ """
592
+ Description : Call function clean_text to process the whole dataset.
593
+ Input text : An input dataset having each element as a document in the dataset.
594
+ Output : A clean dataset.
595
+ """
596
+
597
+ for i, data_point in enumerate(dataset):
598
+ cleaned_text = clean_text(data_point["text"])
599
+ if cleaned_text != dataset[i]["text"]:
600
+ dataset[i]["text"] = cleaned_text
601
+ dataset[i]["updated_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
602
+
603
+ return [data_point for data_point in dataset if data_point["text"] != ""]
604
+
605
+ def sorted_by_len(keyword_list):
606
+ len_keywords = [[len(kw), kw] for kw in keyword_list]
607
+
608
+ return [kw for _, kw in sorted(len_keywords)][::-1]
609
+
610
+
611
+ PORN_KEYWORDS = [
612
+ "คลิปหลุด",
613
+ "กระเจี๊ยว",
614
+ "คลิปโป๊",
615
+ "หนังโป๊",
616
+ "หนังโป้",
617
+ "หนังโป็",
618
+ "เรื่องเสียว",
619
+ "ซอยหี",
620
+ "ชักว่าว",
621
+ "ท่าหมา",
622
+ "ขย่มควย",
623
+ "เล่นเสียว",
624
+ "ควยใหญ่",
625
+ "หนังเอ็กซ์",
626
+ "แหกหี",
627
+ "เย็ด",
628
+ ]
629
+ GAMBLE_KEYWORDS = [
630
+ "ufabet",
631
+ "UFABET",
632
+ "ล้มโต๊ะ",
633
+ "เซียนสเต็ป",
634
+ "บอลเต็ง",
635
+ "แทงบอล",
636
+ "คาสิโน",
637
+ "บาคาร่า",
638
+ "เว็บสล็อต",
639
+ "เกมสล็อต",
640
+ "สล็อตออนไลน์",
641
+ "คาสิโนออนไลน์",
642
+ "หวยมาเลย์",
643
+ "หวยฮานอย",
644
+ "น้ำเต้าปูปลา",
645
+ "หวยออนไลน์",
646
+ "แทงหวย",
647
+ "หวยหุ้น",
648
+ "ยิง��ลาออนไลน์",
649
+ "ได้เงินจริง",
650
+ "บา คา ร่า",
651
+ ]
652
+ SPAM_MOVIE_KEYWORDS = [
653
+ "ดูหนังออนไลน์",
654
+ "หนังออนไลน์",
655
+ "เว็บดูหนัง",
656
+ "หนังชนโรง",
657
+ "หนังใหม่ชนโรง",
658
+ "เสียงไทย",
659
+ "เสียงญี่ปุ่น",
660
+ "เสียงอังกฤษ",
661
+ ]
662
+ SPAM_LIKE_KEYWORDS = [
663
+ "ปั้มไลค์",
664
+ "รับจ้างกดไลค์",
665
+ "จ้างไลค์",
666
+ "ปั๊มไลค์",
667
+ "ปั่นไลค์",
668
+ "เพิ่มไลค์",
669
+ "ซื้อไลค์",
670
+ ]
671
+ CODE_KEYWORDS = [
672
+ "padding:",
673
+ "display:",
674
+ "S3=n8",
675
+ "phpBB Debug",
676
+ "getElementById",
677
+ "innerHTML",
678
+ "parseInt",
679
+ "addEventListener",
680
+ "console\.log",
681
+ "checkCookieForTarget",
682
+ "setAttribute",
683
+ "getItem",
684
+ "if \(",
685
+ "else {",
686
+ "JSON\.stringify",
687
+ "onclick",
688
+ ]
689
+
690
+ WEBBOARD_KEYWORDS = [
691
+ "คุณกำลังใช้งานแบบปิดการใช้ Javascript",
692
+ "Longdo Dictionary",
693
+ "นโยบายการคุ้มครองข้อมูลส่วนบุคคล",
694
+ "เงื่อนไขการให้บริการเว็บไซต์",
695
+ "นโยบายความปลอดภัย",
696
+ "นโยบายเว็บไซต์และการปฏิเสธความรับผิด",
697
+ "คุณอาจจะยังไม่ได้เข้าสู่ระบบหรือยังไม่ได้ลงทะเบียน",
698
+ "คุณไม่ได้เข้าสู่ระบบหรือคุณไม่มีสิทธิ์เข้าหน้านี้",
699
+ ]
700
+
701
+ PORN_KEYWORDS += [" ".join(list(kw)) for kw in PORN_KEYWORDS]
702
+ GAMBLE_KEYWORDS += [" ".join(list(kw)) for kw in GAMBLE_KEYWORDS]
703
+ SPAM_MOVIE_KEYWORDS += [" ".join(list(kw)) for kw in SPAM_MOVIE_KEYWORDS]
704
+
705
+ DOCUMENT_REMOVAL_KEYWORDS = (
706
+ PORN_KEYWORDS
707
+ + GAMBLE_KEYWORDS
708
+ + SPAM_MOVIE_KEYWORDS
709
+ + SPAM_LIKE_KEYWORDS
710
+ + CODE_KEYWORDS
711
+ + WEBBOARD_KEYWORDS
712
+ )
713
+
714
+ PARTIAL_REMOVAL_KEYWORDS = [
715
+ "Posted on",
716
+ "Posted by",
717
+ "Posted by:",
718
+ "Posted By:",
719
+ "สมาชิกหมายเลข [0-9,]+",
720
+ "อ่าน [0-9,]+ ครั้ง",
721
+ "เปิดดู [0-9,]+ ครั้ง",
722
+ "ดู [0-9,]+ ครั้ง",
723
+ "คะแนนสะสม: [0-9,]+ แต้ม",
724
+ "ความคิดเห็น: [0-9,]+",
725
+ "[0-9,]+ บุคคลทั่วไป กำลังดูบอร์ดนี้",
726
+ "หน้าที่แล้ว ต่อไป",
727
+ "ความคิดเห็นที่ [0-9,]+",
728
+ "[0-9,]+ สมาชิก และ [0-9,]+ บุคคลทั่วไป",
729
+ "กำลังดูหัวข้อนี้",
730
+ "เข้าสู่ระบบด้วยชื่อผู้ใช้",
731
+ "แสดงกระทู้จาก:",
732
+ "กระทู้: [0-9,]+",
733
+ "เว็บไซต์เรามีการใช้คุกกี้และเก็บข้อมูลผู้ใช้งาน โปรดศึกษาและยอมรับ นโยบายคุ้มครองข้อมูลส่วนบุคคล ก่อนใช้งาน",
734
+ "Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use\.",
735
+ "Previous\t\nNext\nLeave a Reply Cancel reply\nYou must be logged in to post a comment.\nSearch for:\nFeatured Post\n",
736
+ "Click to read more\nYou must be logged in to view or write comments\.",
737
+ "[0-9,]+ Views",
738
+ "Skip to content",
739
+ "Last Modified Posts",
740
+ "Last Updated:",
741
+ "\(อ่าน [0-9,]+ ครั้ง\)",
742
+ "Recent Comments",
743
+ "«.*?»",
744
+ "< --แสดงทั้งหมด-- >",
745
+ "นโยบายความเป็นส่วนตัว",
746
+ "เงื่อนไขการใช้เว็บไซต์",
747
+ "ตั้งค่าคุกกี้",
748
+ "ท่านยอมรับให้เว็บไซต์นี้จัดเก็บคุกกี้เพื่อประสบการณ์การใช้งานเว็บไซต์ที่ดียิ่งขึ้น",
749
+ "รวมถึงช่วยให้ท่านมีโอกาสได้รับข้อเสนอหรือเนื้อหาที่ตรงตามความสนใจของท่าน",
750
+ "ท่านสามารถดู Privacy Notice ของเว็บไซต์เรา ได้ที่นี่",
751
+ "You may be trying to access this site from a secured browser on the server. Please enable scripts and reload this page.",
752
+ "เผยแพร่: \d\d [ก-๙]+ \d\d\d\d \d\d:\d\d น\.",
753
+ "Last updated: \d\d [ก-๙]+\.[ก-๙]+\. \d\d\d\d \d\d:\d\d น\.",
754
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit\.",
755
+ "Search for:",
756
+ "Save my name, email, and website in this browser for the next time I comment",
757
+ "Your email address will not be published. Required fields are marked",
758
+ "Leave a Reply Cancel reply",
759
+ "((?:หน้าหลัก|เข้าสู่ระบบ|หน้าแรก) \|(?: [^\s]+(?:(?: \|)|$|\s))+)",
760
+ "กลับหน้าแรก",
761
+ "ติดต่อเรา",
762
+ "Contact Us",
763
+ "#\w+",
764
+ "ติดต่อผู้ดูแลเว็บไซต์",
765
+ "หากท่านพบว่ามีข้อมูลใดๆที่ละเมิดทรัพย์สินทางปัญญาปรากฏอยู่ในเว็บไซต์โปรดแจ้งให้ทราบ",
766
+ "No related posts",
767
+ "Posted in",
768
+ "((?:Tags:|Tagged|Tag) (?:.{1,40}(?:,|\n|$))+)",
769
+ "ตอบ:",
770
+ "Sort by:",
771
+ "All rights reserved",
772
+ "ความยาวอย่างน้อย",
773
+ "ระบบได้ดำเนินการส่ง OTP",
774
+ "เป็นสมาชิกอยู่แล้ว\?",
775
+ "We use cookies",
776
+ "Cookie Settings",
777
+ "Homeหน้าหลัก",
778
+ "Home หน้าหลัก",
779
+ "ข่าวสารล่าสุด",
780
+ "ปัญหา การใช้งาน",
781
+ "ปัญหาการใช้งาน" "ผู้เขียน",
782
+ "หัวข้อ:",
783
+ "\*\* พร้อมส่ง \*\*",
784
+ ]
785
+
786
+ TH_MONTHS = [
787
+ "มกราคม",
788
+ "กุมภาพันธ์",
789
+ "มีนาคม",
790
+ "เมษายน",
791
+ "พฤษภาคม",
792
+ "มิถุนายน",
793
+ "กรกฎาคม",
794
+ "สิงหาคม",
795
+ "กันยายน",
796
+ "ตุลาคม",
797
+ "พฤศจิกายน",
798
+ "ธันวาคม",
799
+ "ม\.ค\.",
800
+ "ก\.พ\.",
801
+ "มี\.ค\.",
802
+ "เม\.ย\.",
803
+ "พ\.ค\.",
804
+ "มิ\.ย\.",
805
+ "ก\.ค\.",
806
+ "ส\.ค\.",
807
+ "ก\.ย\.",
808
+ "ต\.ค\.",
809
+ "พ\.ย\.",
810
+ "ธ\.ค\.",
811
+ ]
812
+
813
+ CODE_SPECIAL_CHARACTERS = ["\{", "\+", "\}", "/", ":"]
814
+
815
+ PARTIAL_REMOVAL_KEYWORDS = sorted_by_len(PARTIAL_REMOVAL_KEYWORDS)
816
+
817
+ import re
818
+ from pythainlp.util import countthai
819
+ from typing import List, Dict
820
+ from datetime import datetime
821
+
822
+
823
+ def contains_document_removal_keywords(text: str) -> bool:
824
+ """
825
+ Description : Check if an input document contains any document removal keywords.
826
+ Input text : An input document.
827
+ Output : True if the document contains the keywords. Otherwise, False
828
+ """
829
+
830
+ pattern = "|".join(DOCUMENT_REMOVAL_KEYWORDS)
831
+
832
+ return bool(re.search(pattern, text))
833
+
834
+
835
+ def check_ratio_bad_substring(text: str) -> bool:
836
+ """
837
+ Description : Check if the ratio between number of keywords and length of a document
838
+ is exceeds the threshold for each groups.
839
+
840
+ Group #1 : Name of months in Thai including abbreviations.
841
+ Group #2 : Special char that usually found in the code section.
842
+ Group #3 : Space.
843
+ Group #4 : Commar.
844
+
845
+ Note : Thresholds of each group are from the experiment on oscar.
846
+
847
+ Input text : An input document.
848
+ Output : True if a ratio of at least 1 group is above . Otherwise, False
849
+ """
850
+
851
+ n = len(text)
852
+
853
+ if len(re.findall("|".join(TH_MONTHS), text)) / n > 0.015:
854
+ return True
855
+
856
+ if len(re.findall("|".join(CODE_SPECIAL_CHARACTERS), text)) / n > 0.075:
857
+ return True
858
+
859
+ if len(re.findall(" ", text)) / n > 0.13:
860
+ return True
861
+
862
+ if len(re.findall(",", text)) / n > 0.05:
863
+ return True
864
+ return False
865
+
866
+
867
+ def remove_partial_keywords(text: str) -> str:
868
+ """
869
+ Description : Remove partial removal keywords from the document.
870
+
871
+ Input text : An input document.
872
+ Output : A document after removed keywords.
873
+ """
874
+
875
+ return re.sub("|".join(PARTIAL_REMOVAL_KEYWORDS), "", text)
876
+
877
+
878
+ def clean_oscar_text(text: str) -> str:
879
+ """
880
+ Description : Clean an input document by these steps
881
+
882
+ 1. Remove the whole document if
883
+ 1.1. Contains any document removal keywords (ex. porn, gamble)
884
+ 1.2. Contains too much TH months, code character, space and commar.
885
+ 1.3. The percent of thai characters is less than 50%.
886
+ 2. Remove partial removal keywords.
887
+
888
+ Input text : An input document.
889
+ Output : A clean document ("" if the whole document should be removed).
890
+ """
891
+
892
+ if (
893
+ len(text) == 0
894
+ or contains_document_removal_keywords(text)
895
+ or check_ratio_bad_substring(text)
896
+ or countthai(text) < 50
897
+ ):
898
+ return ""
899
+
900
+ text = remove_partial_keywords(text).strip()
901
+
902
+ return text
903
+
904
+
905
+ def clean_dataset(dataset: List[Dict[str, str]]) -> List[Dict[str, str]]:
906
+ """
907
+ Description : Call function clean_text to process the whole dataset.
908
+ Input text : An input dataset having each element as a document in the dataset.
909
+ Output : A clean dataset.
910
+ """
911
+ for i, data_point in enumerate(dataset):
912
+ cleaned_text = clean_text(data_point["text"])
913
+ if cleaned_text != dataset[i]["text"]:
914
+ dataset[i]["text"] = cleaned_text
915
+ dataset[i]["updated_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
916
+
917
+ return [data_point for data_point in dataset if data_point["text"] != ""]
step2.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import unicodedata
3
+
4
+ UNICODE_PUNCT = {
5
+ ",": ",",
6
+ "。": ".",
7
+ "、": ",",
8
+ "„": '"',
9
+ "”": '"',
10
+ "“": '"',
11
+ "«": '"',
12
+ "»": '"',
13
+ "1": '"',
14
+ "」": '"',
15
+ "「": '"',
16
+ "《": '"',
17
+ "》": '"',
18
+ "´": "'",
19
+ "∶": ":",
20
+ ":": ":",
21
+ "?": "?",
22
+ "!": "!",
23
+ "(": "(",
24
+ ")": ")",
25
+ ";": ";",
26
+ "–": "-",
27
+ "—": " - ",
28
+ ".": ". ",
29
+ "~": "~",
30
+ "’": "'",
31
+ "…": "...",
32
+ "━": "-",
33
+ "〈": "<",
34
+ "〉": ">",
35
+ "【": "[",
36
+ "】": "]",
37
+ "%": "%",
38
+ "►": "-",
39
+ }
40
+
41
+ UNICODE_PUNCT_RE = re.compile(f"[{''.join(UNICODE_PUNCT.keys())}]")
42
+
43
+
44
+ def replace_unicode_punct(text: str) -> str:
45
+ return "".join((UNICODE_PUNCT.get(c, c) for c in text))
46
+
47
+
48
+ def remove_unicode_punct(text: str) -> str:
49
+ """More aggressive version of replace_unicode_punct but also faster."""
50
+ return UNICODE_PUNCT_RE.sub("", text)
51
+
52
+
53
+ def strip_accents(line: str) -> str:
54
+ """Strips accents from a piece of text."""
55
+ nfd = unicodedata.normalize("NFD", line)
56
+ output = [c for c in nfd if unicodedata.category(c) != "Mn"]
57
+ if len(output) == line:
58
+ return line
59
+ return "".join(output)
60
+
61
+
62
+ # Build a regex matching all control characters.
63
+ NON_PRINTING_CHARS_RE = re.compile(
64
+ f"[{''.join(map(chr, list(range(0,32)) + list(range(127,160))))}]"
65
+ )
66
+ DIGIT_RE = re.compile(r"\d")
67
+ PUNCT_OR_NON_PRINTING_CHARS_RE = re.compile(
68
+ (UNICODE_PUNCT_RE.pattern + NON_PRINTING_CHARS_RE.pattern).replace("][", "")
69
+ )
70
+
71
+
72
+ def remove_non_printing_char(text: str) -> str:
73
+ return NON_PRINTING_CHARS_RE.sub("", text)
74
+
75
+
76
+ def normalize_spacing_for_tok(text: str, language: str = "en") -> str:
77
+ res = (
78
+ text.replace("\r", "")
79
+ # remove extra spaces
80
+ .replace("(", " (")
81
+ .replace(")", ") ")
82
+ .replace(" +", " ")
83
+ )
84
+ res = re.sub(r"\) ([\.\!\:\?\;\,])", r"\)\1", res)
85
+ res = res.replace("( ", "(").replace(" )", ")")
86
+ res = re.sub(r"(\d) \%", r"\1\%", res)
87
+ res = res.replace(" :", ":").replace(" ;", ";")
88
+ res = res.replace("`", "'").replace("''", ' " ')
89
+
90
+ res = (
91
+ res.replace("„", '"')
92
+ .replace("“", '"')
93
+ .replace("”", '"')
94
+ .replace("–", "-")
95
+ .replace("—", " - ")
96
+ .replace(" +", " ")
97
+ .replace("´", "'")
98
+ .replace("([a-z])‘([a-z])", r"\1'\2/")
99
+ .replace("([a-z])’([a-z])", r"\1'\2/")
100
+ .replace("‘", '"')
101
+ .replace("‚", '"')
102
+ .replace("’", '"')
103
+ .replace("''", '"')
104
+ .replace("´´", '"')
105
+ .replace("…", "...")
106
+ # French quotes
107
+ .replace(" « ", ' "')
108
+ .replace("« ", '"')
109
+ .replace("«", '"')
110
+ .replace(" » ", '" ')
111
+ .replace(" »", '"')
112
+ .replace("»", '"')
113
+ # handle pseudo-spaces
114
+ .replace(" %", "%")
115
+ .replace("nº ", "nº ")
116
+ .replace(" :", ":")
117
+ .replace(" ºC", " ºC")
118
+ .replace(" cm", " cm")
119
+ .replace(" ?", "?")
120
+ .replace(" !", "!")
121
+ .replace(" ;", ";")
122
+ .replace(", ", ", ")
123
+ .replace(" +", " ")
124
+ .replace(".", ". ")
125
+ )
126
+ # English "quotation," followed by comma, style
127
+ if language == "en":
128
+ res = re.sub(r"\"([,\.]+)", r"\1\"", res)
129
+ # Czech is confused
130
+ elif language == "cs" or language == "cz":
131
+ pass
132
+ # German/Spanish/French "quotation", followed by comma, style
133
+ else:
134
+ res = res.replace(',"', '",')
135
+ res = re.sub(
136
+ r"(\.+)\"(\s*[^<])", r"\"\1\2", res
137
+ ) # don't fix period at end of sentence
138
+
139
+ if (
140
+ language == "de"
141
+ or language == "es"
142
+ or language == "cz"
143
+ or language == "cs"
144
+ or language == "fr"
145
+ ):
146
+ res = re.sub(r"(\d) (\d)", r"\1,\2", res)
147
+ else:
148
+ res = re.sub(r"(\d) (\d)", r"\1.\2", res)
149
+ return res
150
+
151
+
152
+ def normalize(line: str, accent=True, case=True, numbers=True, punct=1) -> str:
153
+ line = line.strip()
154
+ if not line:
155
+ return line
156
+ if case:
157
+ line = line.lower()
158
+ if accent:
159
+ line = strip_accents(line)
160
+ if numbers:
161
+ line = DIGIT_RE.sub("0", line)
162
+ if punct == 1:
163
+ line = replace_unicode_punct(line)
164
+ elif punct == 2:
165
+ line = remove_unicode_punct(line)
166
+ line = remove_non_printing_char(line)
167
+ return line
168
+
169
+
170
+ def slow_normalize_for_dedup(line: str) -> str:
171
+ return normalize(line, accent=False, case=True, numbers=True, punct=2)
172
+
173
+
174
+ def normalize_for_dedup(line: str) -> str:
175
+ line = line.strip()
176
+ if not line:
177
+ return line
178
+ # case
179
+ line = line.lower()
180
+ # numbers
181
+ line = DIGIT_RE.sub("0", line)
182
+ line = PUNCT_OR_NON_PRINTING_CHARS_RE.sub("", line)
183
+ return line
step2_perplexity.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import kenlm
2
+ import math
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pickle
6
+ import scipy
7
+ import sentencepiece # type: ignore
8
+ from typing import List
9
+ import warnings
10
+ from step2 import normalize
11
+ import os
12
+ warnings.simplefilter(action="ignore", category=FutureWarning)
13
+
14
+
15
+ def load_model():
16
+ os.system("gdown 1OBbo21v_-esL31rxtNtsMHrA8T1JYqAd")
17
+ os.system("unzip /content/core.zip")
18
+ os.system("!rm /content/core.zip")
19
+
20
+ class SentencesLM:
21
+ """Returns the score of each individual paragraph."""
22
+
23
+ def __init__(self):
24
+ lm_config = kenlm.Config()
25
+ lm_config.load_method = 2
26
+
27
+ lm_model_filename = (
28
+ os.path.join("core", "th.arpa.bin")
29
+ )
30
+ self.lm = kenlm.Model(str(lm_model_filename), lm_config)
31
+ self.sp = sentencepiece.SentencePieceProcessor()
32
+ self.sp.load(
33
+ os.path.join("core", "th.sp.model")
34
+ )
35
+
36
+ def pp(self, log_score: float, length: int) -> float:
37
+ """Compute perplexity score"""
38
+ power = min(30, -log_score / length)
39
+
40
+ return 10.0**power
41
+
42
+ def do(self, document: List[str]) -> float: # type: ignore
43
+ """Compute perplexity for each line of document"""
44
+ total_pp = 0
45
+ total_length = 0
46
+ for line in document:
47
+ line = normalize(line, accent=False)
48
+ tokenized_line = " ".join(self.sp.encode_as_pieces(line))
49
+ log_score = self.lm.score(tokenized_line)
50
+ length = len(line.split()) + 1
51
+
52
+ total_length += length
53
+ total_pp += log_score
54
+ return round(self.pp(total_pp, total_length), 1)
55
+
56
+
57
+ classifier_filename = os.path.join("core", "decision_tree.sav")
58
+ classifier = pickle.load(open(classifier_filename, "rb"))
59
+
60
+ lm = SentencesLM()
61
+
62
+
63
+ def classify_spam(text: str):
64
+ """Classify if text is spam using perplexity and decision tree as thresholder
65
+ Input : text -> a text to classify.
66
+ Output : prediction -> Prediction whether text is spam.
67
+ 1 Represents spam and 0 represent non-spam.
68
+ Output : log_pp_score -> log of perplexity score.
69
+ """
70
+
71
+ pp_score = lm.do(text.split("\n"))
72
+
73
+ log_pp_score = math.log(pp_score)
74
+
75
+ prediction = classifier.predict(pd.DataFrame({"log_score": [log_pp_score]}))
76
+
77
+ return prediction, log_pp_score
78
+
79
+
80
+ def sample_score(log_scores, n, percentage=0.1) -> np.ndarray:
81
+ np.random.seed(0)
82
+
83
+ lower_bound, upper_bound = min(log_scores), max(log_scores)
84
+
85
+ mean, std = np.mean(log_scores), np.std(log_scores)
86
+
87
+ sampled_scores = scipy.stats.truncnorm.rvs(
88
+ (lower_bound - mean) / std,
89
+ (upper_bound - mean) / std,
90
+ loc=mean,
91
+ scale=std,
92
+ size=int(percentage * n),
93
+ )
94
+
95
+ return sampled_scores
96
+
97
+
98
+ def sample_text_back(texts, log_scores, percentage=0.5, replace=True) -> List[str]:
99
+ """Sample some spam text back in the dataset
100
+ using log score distribution of language model"""
101
+
102
+ sampled_scores = sample_score(log_scores, len(texts), percentage)
103
+
104
+ sampled_texts = []
105
+
106
+ selected_idx = set()
107
+
108
+ for samp_score in sampled_scores:
109
+ min_diff, min_idx = float("inf"), -1
110
+
111
+ for idx, s in enumerate(log_scores):
112
+ if idx in selected_idx:
113
+ continue
114
+
115
+ diff = (samp_score - s) ** 2
116
+ if diff < min_diff:
117
+ min_diff = diff
118
+ min_idx = idx
119
+
120
+ sampled_texts.append(texts[min_idx])
121
+
122
+ if not replace:
123
+ selected_idx.add(min_idx)
124
+
125
+ return sampled_texts