โครงการนี้ใช้อัลกอริทึมที่กำหนดเองเพื่อแยกประโยคและคำหลักที่สำคัญที่สุดจากบทความข่าวภาษาสเปนและภาษาอังกฤษ
มันได้รับการพัฒนาอย่างเต็มที่ใน Python และได้รับแรงบันดาลใจจากโครงการที่คล้ายกันที่เห็นใน Reddit News subreddits ที่ใช้ความถี่เอกสารความถี่ - Inverse ( tf–idf )
3 ไฟล์ที่สำคัญที่สุดคือ:
scraper.py : สคริปต์ Python ที่ดำเนินการขูดเว็บบนแหล่ง HTML ที่กำหนดมันแยกชื่อบทความวันที่และร่างกาย
summary.py : สคริปต์ Python ที่ใช้อัลกอริทึมที่กำหนดเองกับสตริงของข้อความและแยกประโยคและคำที่ติดอันดับสูงสุด
bot.py : บอท reddit ที่ตรวจสอบ subreddit สำหรับการส่งล่าสุด มันจัดการรายการของการส่งที่ผ่านการประมวลผลแล้วเพื่อหลีกเลี่ยงการทำซ้ำ
โครงการนี้ใช้ไลบรารี Python ต่อไปนี้
spaCy : ใช้เพื่อโทเค็นบทความเป็นประโยคและคำพูดPRAW : ใช้ประโยชน์จาก Reddit API ได้ง่ายมากRequests : เพื่อดำเนินการ HTTP get คำขอไปยัง URL บทความBeautifulSoup : ใช้สำหรับการแยกข้อความบทความhtml5lib : ตัวแยกวิเคราะห์นี้เข้ากันได้ดีขึ้นเมื่อใช้กับ BeautifulSouptldextract : ใช้เพื่อแยกโดเมนออกจาก URLwordcloud : ใช้เพื่อสร้างคลาวด์คำด้วยข้อความบทความ หลังจากติดตั้งไลบรารี spaCy คุณต้องติดตั้งรูปแบบภาษาเพื่อให้สามารถทำบทความได้
สำหรับ Spanish คุณสามารถเรียกใช้อันนี้:
python -m spacy download es_core_news_sm
สำหรับภาษาอื่น ๆ โปรดตรวจสอบลิงค์ต่อไปนี้: https://spacy.io/usage/models
บอทนั้นเรียบง่ายในธรรมชาติมันใช้ไลบรารี PRAW ซึ่งตรงไปตรงมามากในการใช้งาน บอทโพล subreddit ทุก ๆ 10 นาทีเพื่อรับผลงานล่าสุด
มันตรวจพบก่อนว่าการส่งยังไม่ได้รับการประมวลผลแล้วตรวจสอบว่า URL ที่ส่งมานั้นอยู่ใน Whitelist หรือไม่ ผู้ทำรายการที่อนุญาตนี้ได้รับการดูแลด้วยตัวเองในปัจจุบัน
หากโพสต์และ URL ของมันผ่านการตรวจสอบทั้งสองกระบวนการของการขูดเว็บจะถูกนำไปใช้กับ URL นี่คือสิ่งที่สิ่งต่าง ๆ เริ่มน่าสนใจ
ก่อนที่จะตอบกลับการส่งต้นฉบับจะตรวจสอบเปอร์เซ็นต์ของการลดลงถ้ามันต่ำเกินไปหรือสูงเกินไปมันข้ามมันและย้ายไปที่การส่งครั้งต่อไป
ขณะนี้อยู่ใน Whitelist มีเว็บไซต์และบล็อกข่าวที่แตกต่างกันมากกว่า 300 เว็บไซต์ การสร้างเครื่องขูดเว็บแบบพิเศษสำหรับแต่ละรายการนั้นไม่สามารถทำได้
สิ่งที่ดีที่สุดอันดับสองที่ควรทำคือทำให้มีดโกนแม่นยำที่สุดเท่าที่จะทำได้
เราเริ่มต้นใช้งานเว็บได้ตามปกติด้วย Requests และห้องสมุด BeautifulSoup
with requests . get ( article_url ) as response :
if response . encoding == "ISO-8859-1" :
response . encoding = "utf-8"
html_source = response . text
for item in [ "</p>" , "</blockquote>" , "</div>" , "</h2>" , "</h3>" ]:
html_source = html_source . replace ( item , item + " n " )
soup = BeautifulSoup ( html_source , "html5lib" ) ไม่กี่ครั้งที่ฉันได้รับปัญหาการเข้ารหัสที่เกิดจากการคาดเดาการเข้ารหัสที่ไม่ถูกต้อง เพื่อหลีกเลี่ยงปัญหานี้ฉันบังคับให้ Requests ด้วย utf-8
ตอนนี้เรามีบทความของเราแยกวิเคราะห์เป็นวัตถุ soup เราจะเริ่มต้นด้วยการแยกชื่อและเวลาที่เผยแพร่
ฉันใช้วิธีการที่คล้ายกันเพื่อแยกค่าทั้งสองฉันตรวจสอบแท็กที่พบบ่อยที่สุดและทางเลือกกลับไปยังทางเลือกทั่วไปถัดไป
ไม่ใช่ทุกเว็บไซต์ที่เปิดเผยวันที่เผยแพร่บางครั้งเราจบลงด้วยสตริงที่ว่างเปล่า
article_title = soup . find ( "title" ). text . replace ( " n " , " " ). strip ()
# If our title is too short we fallback to the first h1 tag.
if len ( article_title ) <= 5 :
article_title = soup . find ( "h1" ). text . replace ( " n " , " " ). strip ()
article_date = ""
# We look for the first meta tag that has the word 'time' in it.
for item in soup . find_all ( "meta" ):
if "time" in item . get ( "property" , "" ):
clean_date = item [ "content" ]. split ( "+" )[ 0 ]. replace ( "Z" , "" )
# Use your preferred time formatting.
article_date = "{:%d-%m-%Y a las %H:%M:%S}" . format (
datetime . fromisoformat ( clean_date ))
break
# If we didn't find any meta tag with a datetime we look for a 'time' tag.
if len ( article_date ) <= 5 :
try :
article_date = soup . find ( "time" ). text . strip ()
except :
passเมื่อแยกข้อความจากแท็กที่แตกต่างกันฉันมักจะได้รับสตริงโดยไม่ต้องแยก ฉันใช้แฮ็คเล็กน้อยเพื่อเพิ่มบรรทัดใหม่ในแต่ละแท็กที่มักจะมีข้อความ สิ่งนี้ปรับปรุงความแม่นยำโดยรวมของโทเคนิเซอร์อย่างมีนัยสำคัญ
แนวคิดดั้งเดิมของฉันคือการยอมรับเฉพาะเว็บไซต์ที่ใช้แท็ก <article> > มันใช้งานได้ดีสำหรับเว็บไซต์แรกที่ฉันทดสอบ แต่ในไม่ช้าฉันก็รู้ว่ามีเว็บไซต์เพียงไม่กี่แห่งที่ใช้มันและผู้ที่ใช้มันไม่ได้ใช้งานอย่างถูกต้อง
article = soup . find ( "article" ). text เมื่อเข้าถึงคุณสมบัติ .text ของแท็ก <article> ฉันสังเกตเห็นว่าฉันได้รับรหัส JavaScript ฉันย้อนกลับเล็กน้อยและลบแท็กทั้งหมดซึ่งสามารถเพิ่ม เสียงรบกวน ลงในข้อความบทความ
[ tag . extract () for tag in soup . find_all (
[ "script" , "img" , "ul" , "time" , "h1" , "h2" , "h3" , "iframe" , "style" , "form" , "footer" , "figcaption" ])]
# These class names/ids are known to add noise or duplicate text to the article.
noisy_names = [ "image" , "img" , "video" , "subheadline" ,
"hidden" , "tract" , "caption" , "tweet" , "expert" ]
for tag in soup . find_all ( "div" ):
tag_id = tag [ "id" ]. lower ()
for item in noisy_names :
if item in tag_id :
tag . extract ()รหัสข้างต้นลบคำอธิบายภาพส่วนใหญ่ซึ่งมักจะทำซ้ำสิ่งที่อยู่ภายในบทความ
หลังจากนั้นฉันใช้กระบวนการ 3 ขั้นตอนเพื่อรับข้อความบทความ
ก่อนอื่นฉันตรวจสอบแท็ก <article> ทั้งหมดและคว้าแท็กด้วยข้อความที่ยาวที่สุด
article = ""
for article_tag in soup . find_all ( "article" ):
if len ( article_tag . text ) >= len ( article ):
article = article_tag . text ที่ใช้งานได้ดีสำหรับเว็บไซต์ที่ใช้แท็ก <article> อย่างถูกต้อง แท็กที่ยาวที่สุดมักจะมีบทความหลักเสมอ
แต่นั่นก็ไม่ได้ผลอย่างที่คาดไว้ฉันสังเกตเห็นคุณภาพที่ไม่ดีในผลลัพธ์บางครั้งฉันก็ได้รับข้อความที่ตัดตอนมาสำหรับบทความอื่น ๆ
นั่นคือเมื่อฉันตัดสินใจที่จะเพิ่มทางเลือก, lnstead เพียงมองหาแท็ก <article> ฉันจะมองหาแท็ก <div> และ <section> ที่มี id's ที่ใช้กันทั่วไป
# These names commonly hold the article text.
common_names = [ "artic" , "summary" , "cont" , "note" , "cuerpo" , "body" ]
# If the article is too short we look somewhere else.
if len ( article ) <= 650 :
for tag in soup . find_all ([ "div" , "section" ]):
tag_id = tag [ "id" ]. lower ()
for item in common_names :
if item in tag_id :
# We guarantee to get the longest div.
if len ( tag . text ) >= len ( article ):
article = tag . text นั่นเพิ่มความแม่นยำเล็กน้อยฉันทำซ้ำรหัส แต่แทนที่จะเป็นแอตทริบิวต์ id ฉันก็กำลังมองหาแอตทริบิวต์ class
# The article is still too short, let's try one more time.
if len ( article ) <= 650 :
for tag in soup . find_all ([ "div" , "section" ]):
tag_class = "" . join ( tag [ "class" ]). lower ()
for item in common_names :
if item in tag_class :
# We guarantee to get the longest div.
if len ( tag . text ) >= len ( article ):
article = tag . textการใช้วิธีการก่อนหน้านี้ทั้งหมดเพิ่มความแม่นยำโดยรวมของมีดโกนอย่างมาก ในบางกรณีฉันใช้คำบางส่วนที่แบ่งปันตัวอักษรเดียวกันเป็นภาษาอังกฤษและสเปน (บทความ -> บทความ/articulo) ขณะนี้มีดโกนเข้ากันได้กับ URL ทั้งหมดที่ฉันทดสอบ
เราทำการตรวจสอบขั้นสุดท้ายและหากบทความยังสั้นเกินไปเรายกเลิกกระบวนการและย้ายไปยัง URL ถัดไปไม่เช่นนั้นเราจะย้ายไปยังอัลกอริทึมสรุป
อัลกอริทึมนี้ได้รับการออกแบบมาเพื่อทำงานเป็นหลักในบทความที่เขียนด้วยภาษาสเปน ประกอบด้วยหลายขั้นตอน:
ก่อนเริ่มต้นเราต้องเริ่มต้นไลบรารี spaCy
NLP = spacy . load ( "es_core_news_sm" ) รหัสบรรทัดนั้นจะโหลดโมเดล Spanish ซึ่งฉันใช้มากที่สุด หากคุณใช้ภาษาอื่นโปรดดูส่วน Requirements เพื่อให้คุณทราบวิธีการติดตั้งรูปแบบที่เหมาะสม
เมื่อแยกข้อความออกจากบทความเรามักจะได้รับช่องว่างจำนวนมากส่วนใหญ่มาจากการแบ่งบรรทัด ( n )
เราแยกข้อความด้วยอักขระนั้นจากนั้นถอดช่องว่างทั้งหมดและเข้าร่วมอีกครั้ง สิ่งนี้ไม่จำเป็นต้องทำอย่างเคร่งครัด แต่ช่วยได้มากในขณะที่ดีบักกระบวนการทั้งหมด
ที่ด้านบนของสคริปต์เราประกาศเส้นทางของไฟล์ข้อความหยุดคำ คำหยุดเหล่านี้จะถูกเพิ่มเข้าไปใน set รับประกันว่าจะไม่มีการทำซ้ำ
ฉันยังเพิ่มรายการด้วยคำภาษาสเปนและภาษาอังกฤษบางคำที่ไม่หยุดคำ แต่พวกเขาไม่ได้เพิ่มอะไรเลยในบทความ ความชอบส่วนตัวของฉันคือการเขียนโค้ดให้ยากในรูปแบบตัวพิมพ์เล็ก
จากนั้นฉันเพิ่มสำเนาของแต่ละคำในตัวพิมพ์ใหญ่และแบบฟอร์มชื่อ ซึ่งหมายความว่า set จะมีขนาดดั้งเดิม 3 เท่า
with open ( ES_STOPWORDS_FILE , "r" , encoding = "utf-8" ) as temp_file :
for word in temp_file . read (). splitlines ():
COMMON_WORDS . add ( word )
with open ( EN_STOPWORDS_FILE , "r" , encoding = "utf-8" ) as temp_file :
for word in temp_file . read (). splitlines ():
COMMON_WORDS . add ( word )
extra_words = list ()
for word in COMMON_WORDS :
extra_words . append ( word . title ())
extra_words . append ( word . upper ())
for word in extra_words :
COMMON_WORDS . add ( word ) ก่อนที่จะเริ่มต้นคำพูดของเราเราจะต้องส่งบทความที่ทำความสะอาดของเราไปยังไปป์ไลน์ NLP ก่อนสิ่งนี้จะทำด้วยรหัสบรรทัดเดียว
doc = NLP ( cleaned_article ) วัตถุ doc นี้มีตัววนซ้ำหลายตัวที่เราจะใช้คือ tokens และ sents (ประโยค)
ณ จุดนี้ฉันได้เพิ่มสัมผัสส่วนบุคคลในอัลกอริทึม ก่อนอื่นฉันทำสำเนาของบทความแล้วลบคำทั่วไปทั้งหมดออกจากมัน
หลังจากนั้นฉันใช้คอ collections.Counter เพื่อทำคะแนนเริ่มต้น
จากนั้นฉันใช้โบนัสตัวคูณกับคำที่เริ่มต้นในตัวพิมพ์ใหญ่และมีความเท่าเทียมกันหรือนานกว่า 4 อักขระ เวลาส่วนใหญ่คำเหล่านั้นเป็นชื่อของสถานที่คนหรือองค์กร
ในที่สุดฉันก็ตั้งค่าเป็นศูนย์คะแนนสำหรับทุกคำที่เป็นตัวเลขจริง
words_of_interest = [
token . text for token in doc if token . text not in COMMON_WORDS ]
scored_words = Counter ( words_of_interest )
for word in scored_words :
if word [ 0 ]. isupper () and len ( word ) >= 4 :
scored_words [ word ] *= 3
if word . isdigit ():
scored_words [ word ] = 0ตอนนี้เรามีคะแนนสุดท้ายสำหรับแต่ละคำแล้วถึงเวลาที่จะทำคะแนนแต่ละประโยคจากบทความ
ในการทำเช่นนี้ก่อนอื่นเราต้องแยกบทความออกเป็นประโยค ฉันลองวิธีการต่าง ๆ รวมถึง RegEx แต่วิธีที่ดีที่สุดคือห้องสมุด spaCy
เราจะวนซ้ำอีกครั้งในวัตถุ doc ที่เรากำหนดไว้ในขั้นตอนก่อนหน้า แต่คราวนี้เราจะทำซ้ำคุณสมบัติ sents
สิ่งที่ควรทราบคือเราสร้างรายการ tokens ประโยคและภายในโทเค็นเหล่านั้นเราสามารถดึงข้อความประโยคได้โดยการเข้าถึงคุณสมบัติ text ของพวกเขา
article_sentences = [ sent for sent in doc . sents ]
scored_sentences = list ()
or index , sent in enumerate ( article_sentences ):
# In some edge cases we have duplicated sentences, we make sure that doesn't happen.
if sent . text not in [ sent for score , index , sent in scored_sentences ]:
scored_sentences . append (
[ score_line ( sent , scored_words ), index , sent . text ]) scored_sentences เป็นรายการของรายการ แต่ละรายการภายในมี 3 ค่า คะแนนประโยคดัชนีและประโยคเอง ค่าเหล่านั้นจะถูกใช้ในขั้นตอนต่อไป
รหัสด้านล่างแสดงวิธีการทำคะแนน
def score_line ( line , scored_words ):
# We remove the common words.
cleaned_line = [
token . text for token in line if token . text not in COMMON_WORDS ]
# We now sum the total number of ocurrences for all words.
temp_score = 0
for word in cleaned_line :
temp_score += scored_words [ word ]
# We apply a bonus score to sentences that contain financial information.
line_lowercase = line . text . lower ()
for word in FINANCIAL_WORDS :
if word in line_lowercase :
temp_score *= 1.5
break
return temp_score เราใช้ตัวคูณกับประโยคที่มีคำใด ๆ ที่หมายถึงเงินหรือการเงิน
นี่คือส่วนสุดท้ายของอัลกอริทึมเราใช้ประโยชน์จากฟังก์ชั่น sorted() เพื่อรับประโยคสูงสุดแล้วสั่งซื้อใหม่ในตำแหน่งเดิม
เราเรียงลำดับ scored_sentences ตามลำดับย้อนกลับสิ่งนี้จะทำให้เราได้รับประโยคที่ได้คะแนนสูงสุดก่อน เราเริ่มต้นตัวแปรตัวนับขนาดเล็กดังนั้นมันจะแตกวนเมื่อมันกระทบ 5 เรายังทิ้งประโยคทั้งหมดที่มี 3 อักขระหรือน้อยกว่า (บางครั้งมีอักขระที่มีความกว้างเป็นศูนย์)
top_sentences = list ()
counter = 0
for score , index , sentence in sorted ( scored_sentences , reverse = True ):
if counter >= 5 :
break
# When the article is too small the sentences may come empty.
if len ( sentence ) >= 3 :
# We append the sentence and its index so we can sort in chronological order.
top_sentences . append ([ index , sentence ])
counter += 1
return [ sentence for index , sentence in sorted ( top_sentences )]ในตอนท้ายเราใช้ความเข้าใจในรายการเพื่อส่งคืนเฉพาะประโยคที่เรียงลำดับตามลำดับเวลาแล้ว
เพื่อความสนุกสนานฉันเพิ่มคำว่าคลาวด์ในแต่ละบทความ ในการทำเช่นนั้นฉันใช้ไลบรารี wordcloud ไลบรารีนี้ใช้งานง่ายมากคุณต้องประกาศวัตถุ WordCloud และใช้วิธี generate ด้วยสตริงของข้อความเป็นพารามิเตอร์
wc = wordcloud . WordCloud () # See cloud.py for full parameters.
wc . generate ( prepared_article )
wc . to_file ( "./temp.png" ) หลังจากสร้างภาพฉันอัปโหลดไปยัง Imgur แล้วกลับไปที่ลิงค์ URL และเพิ่มลงในข้อความ Markdown

นี่เป็นโครงการที่สนุกและน่าสนใจมากในการทำงาน ฉันอาจจะคิดค้นล้อใหม่ แต่อย่างน้อยฉันก็เรียนรู้สิ่งดีๆสักสองสามอย่าง
ฉันพอใจกับคุณภาพโดยรวมของผลลัพธ์และฉันจะปรับแต่งอัลกอริทึมและใช้การปรับปรุงความเข้ากันได้
เป็นบันทึกด้านข้างเมื่อทดสอบสคริปต์ฉันขอทวีตโดยไม่ตั้งใจโพสต์ Facebook และบทความเขียนภาษาอังกฤษ พวกเขาทั้งหมดได้รับผลลัพธ์ที่ยอมรับได้ แต่เนื่องจากไซต์เหล่านั้นไม่ใช่เป้าหมายที่ฉันลบออกจากผู้อนุญาต
หลังจากข้อเสนอแนะหลายสัปดาห์ฉันตัดสินใจเพิ่มการสนับสนุนภาษาอังกฤษ สิ่งนี้จำเป็นต้องมีการปรับโครงสร้างเล็กน้อย
เพื่อให้ทำงานกับภาษาอื่น ๆ คุณจะต้องใช้ไฟล์ข้อความที่มีคำหยุดทั้งหมดจากภาษาดังกล่าวและคัดลอกรหัสสองสามบรรทัด (ดูลบส่วนคำทั่วไปและหยุดคำ)