Proyek ini mengimplementasikan algoritma khusus untuk mengekstraksi kalimat dan kata kunci terpenting dari artikel berita Spanyol dan Inggris.
Itu sepenuhnya dikembangkan di Python dan terinspirasi oleh proyek serupa yang terlihat pada subreddits berita Reddit yang menggunakan istilah frekuensi dokumen -inversal frekuensi ( tf–idf ).
3 file terpenting adalah:
scraper.py : skrip Python yang melakukan pengikisan web pada sumber HTML yang diberikan, ia mengekstraksi judul artikel, tanggal dan tubuh.
summary.py : skrip Python yang menerapkan algoritma khusus untuk serangkaian teks dan mengekstraksi kalimat dan kata -kata peringkat teratas.
bot.py : bot reddit yang memeriksa subreddit untuk pengiriman terbarunya. Ini mengelola daftar pengiriman yang sudah diproses untuk menghindari duplikat.
Proyek ini menggunakan pustaka python berikut
spaCy : Digunakan untuk tokenize artikel menjadi kalimat dan kata -kata.PRAW : Membuat penggunaan API Reddit sangat mudah.Requests : Untuk melakukan http get permintaan ke URL artikel.BeautifulSoup : Digunakan untuk mengekstraksi teks artikel.html5lib : Parser ini mendapatkan kompatibilitas yang lebih baik saat digunakan dengan BeautifulSoup .tldextract : Digunakan untuk mengekstrak domain dari URL.wordcloud : Digunakan untuk membuat awan kata dengan teks artikel. Setelah menginstal perpustakaan spaCy , Anda harus menginstal model bahasa untuk dapat tokenisasi artikel.
Untuk Spanish Anda dapat menjalankan yang ini:
python -m spacy download es_core_news_sm
Untuk bahasa lain, silakan periksa tautan berikut: https://spacy.io/usage/models
Bot itu sederhana di alam, ia menggunakan perpustakaan PRAW yang sangat mudah digunakan. Bot jajak pendapat subreddit setiap 10 menit untuk mendapatkan kiriman terbarunya.
Pertama -tama mendeteksi jika pengiriman belum diproses dan kemudian memeriksa apakah URL pengiriman ada di daftar putih. Daftar putih ini saat ini dikuratori oleh diri saya sendiri.
Jika pos dan URL melewati kedua cek maka proses pengikis web diterapkan pada URL, di sinilah segalanya mulai menjadi menarik.
Sebelum membalas pengiriman asli, ia memeriksa persentase reduksi yang dicapai, jika terlalu rendah atau terlalu tinggi melompati dan bergerak ke pengiriman berikutnya.
Saat ini di daftar putih sudah ada lebih dari 300 situs web artikel dan blog berita yang berbeda. Membuat pencakar web khusus untuk masing -masing tidak layak.
Hal terbaik kedua yang harus dilakukan adalah membuat pengikis seakurat mungkin.
Kami memulai pengikis web dengan cara yang biasa, dengan Requests dan perpustakaan 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" ) Sangat sedikit kali saya mendapatkan masalah penyandian yang disebabkan oleh tebakan penyandian yang salah. Untuk menghindari masalah ini, saya memaksa Requests untuk memecahkan kode dengan utf-8 .
Sekarang setelah kami memiliki artikel yang diuraikan menjadi objek soup , kami akan mulai dengan mengekstraksi judul dan waktu yang diterbitkan.
Saya menggunakan metode serupa untuk mengekstrak kedua nilai, pertama -tama saya memeriksa tag dan fallback yang paling umum ke alternatif umum berikutnya.
Tidak semua situs web mengekspos tanggal yang dipublikasikan, kami terkadang berakhir dengan string kosong.
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 :
passSaat mengekstraksi teks dari tag yang berbeda, saya sering mendapatkan senar tanpa pemisahan. Saya menerapkan sedikit peretasan untuk menambahkan baris baru ke setiap tag yang biasanya berisi teks. Ini secara signifikan meningkatkan akurasi keseluruhan tokenizer.
Ide asli saya adalah hanya menerima situs web yang menggunakan tag <article> . Ini bekerja dengan baik untuk situs web pertama yang saya uji, tetapi saya segera menyadari bahwa sangat sedikit situs web yang menggunakannya dan mereka yang menggunakannya tidak menggunakannya dengan benar.
article = soup . find ( "article" ). text Saat mengakses properti .text dari tag <article> , saya perhatikan saya juga mendapatkan kode JavaScript. Saya mundur sedikit dan menghapus semua tag yang dapat menambahkan noise ke teks artikel.
[ 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 ()Kode di atas menghapus sebagian besar teks, yang biasanya mengulangi apa yang ada di dalam artikel.
Setelah itu saya menerapkan proses 3 langkah untuk mendapatkan teks artikel.
Pertama, saya memeriksa semua tag <article> dan meraih yang dengan teks terpanjang.
article = ""
for article_tag in soup . find_all ( "article" ):
if len ( article_tag . text ) >= len ( article ):
article = article_tag . text Itu berfungsi dengan baik untuk situs web yang menggunakan tag <article> dengan benar. Tag terpanjang hampir selalu berisi artikel utama.
Tapi itu tidak berhasil seperti yang diharapkan, saya perhatikan kualitas yang buruk pada hasilnya, kadang -kadang saya mendapatkan kutipan untuk artikel lain.
Saat itulah saya memutuskan untuk menambahkan fallback, lnstead hanya mencari tag <article> Saya akan mencari tag <div> dan <section> dengan id's yang umum digunakan.
# 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 Itu sedikit meningkatkan akurasi, saya mengulangi kode tetapi alih -alih atribut id saya juga mencari atribut 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 . textMenggunakan semua metode sebelumnya sangat meningkatkan akurasi keseluruhan scraper. Dalam beberapa kasus saya menggunakan kata -kata parsial yang berbagi surat yang sama dalam bahasa Inggris dan Spanyol (artik -> artikel/artikulo). Scraper sekarang kompatibel dengan semua URL yang saya uji.
Kami melakukan pemeriksaan terakhir dan jika artikelnya masih terlalu pendek, kami membatalkan proses dan pindah ke URL berikutnya, jika tidak, kami pindah ke algoritma ringkasan.
Algoritma ini dirancang untuk bekerja terutama pada artikel tertulis Spanyol. Itu terdiri dari beberapa langkah:
Sebelum memulai, kita perlu menginisialisasi Perpustakaan spaCy .
NLP = spacy . load ( "es_core_news_sm" ) Baris kode itu akan memuat model Spanish yang paling saya gunakan. Jika Anda menggunakan bahasa lain, silakan merujuk ke bagian Requirements sehingga Anda tahu cara menginstal model yang sesuai.
Saat mengekstraksi teks dari artikel, kami biasanya mendapatkan banyak whitespace, kebanyakan dari jeda garis ( n ).
Kami membagi teks dengan karakter itu, lalu menelanjangi semua whitespace dan bergabung lagi. Ini tidak sepenuhnya diperlukan untuk melakukannya tetapi banyak membantu sambil men -debug seluruh proses.
Di bagian atas skrip kami mendeklarasikan jalur file teks STOP Words. Kata -kata berhenti ini akan ditambahkan ke satu set , menjamin tidak ada duplikat.
Saya juga menambahkan daftar dengan beberapa kata Spanyol dan Inggris yang tidak menghentikan kata -kata tetapi mereka tidak menambahkan sesuatu yang substansial pada artikel tersebut. Preferensi pribadi saya adalah membuat kode yang keras dalam bentuk huruf kecil.
Lalu saya menambahkan salinan setiap kata dalam bentuk huruf besar dan judul. Yang berarti set akan menjadi 3 kali ukuran aslinya.
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 ) Sebelum mulai tokenisasi kata -kata kami, kami harus terlebih dahulu meneruskan artikel kami yang dibersihkan ke dalam pipa NLP , ini dilakukan dengan satu baris kode.
doc = NLP ( cleaned_article ) Objek doc ini berisi beberapa iterator, 2 yang akan kami gunakan adalah tokens dan sents (kalimat).
Pada titik ini saya menambahkan sentuhan pribadi ke algoritma. Pertama saya membuat salinan artikel dan kemudian menghapus semua kata umum darinya.
Setelah itu saya menggunakan collections.Counter Objek Counter untuk melakukan penilaian awal.
Kemudian saya menerapkan bonus pengganda pada kata -kata yang dimulai dalam huruf besar dan sama atau lebih dari 4 karakter. Sebagian besar waktu kata -kata itu adalah nama tempat, orang atau organisasi.
Akhirnya saya menetapkan nol skor untuk semua kata yang sebenarnya angka.
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 ] = 0Sekarang kami memiliki skor akhir untuk setiap kata, saatnya untuk mencetak setiap kalimat dari artikel.
Untuk melakukan ini, pertama -tama kita harus membagi artikel menjadi kalimat. Saya mencoba berbagai pendekatan, termasuk RegEx tetapi yang paling berhasil adalah perpustakaan spaCy .
Kami akan beralih lagi atas objek doc yang kami tentukan pada langkah sebelumnya, tetapi kali ini kami akan mengulangi properti sents -nya.
Sesuatu yang perlu diperhatikan adalah bahwa kami membuat daftar tokens kalimat dan di dalam token itu kami dapat mengambil teks kalimat dengan mengakses properti text mereka.
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 adalah daftar daftar. Setiap daftar bagian dalam berisi 3 nilai. Skor kalimat, indeksnya dan kalimat itu sendiri. Nilai -nilai itu akan digunakan pada langkah berikutnya.
Kode di bawah ini menunjukkan bagaimana garis dinilai.
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 Kami menerapkan pengali untuk kalimat yang berisi kata apa pun yang mengacu pada uang atau keuangan.
Ini adalah bagian terakhir dari algoritma, kami memanfaatkan fungsi sorted() untuk mendapatkan kalimat teratas dan kemudian mengatur ulang mereka di posisi semula.
Kami mengurutkan scored_sentences dalam urutan terbalik, ini akan memberi kami kalimat skor teratas terlebih dahulu. Kami memulai variabel penghitung kecil sehingga merusak loop setelah mencapai 5. Kami juga membuang semua kalimat yang 3 karakter atau kurang (kadang-kadang ada karakter lebar nol-lebar).
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 )]Pada akhirnya kami menggunakan pemahaman daftar untuk hanya mengembalikan kalimat yang sudah diurutkan dalam urutan kronologis.
Hanya untuk bersenang -senang, saya menambahkan cloud kata untuk setiap artikel. Untuk melakukannya saya menggunakan Perpustakaan wordcloud . Perpustakaan ini sangat mudah digunakan, Anda hanya perlu mendeklarasikan objek WordCloud dan menggunakan metode generate dengan serangkaian teks sebagai parameternya.
wc = wordcloud . WordCloud () # See cloud.py for full parameters.
wc . generate ( prepared_article )
wc . to_file ( "./temp.png" ) Setelah menghasilkan gambar, saya mengunggahnya ke Imgur , mendapatkan kembali tautan URL dan menambahkannya ke pesan Markdown .

Ini adalah proyek yang sangat menyenangkan dan menarik untuk dikerjakan. Saya mungkin telah menemukan kembali roda tetapi setidaknya saya belajar beberapa hal keren.
Saya puas dengan kualitas hasil keseluruhan dan saya akan terus mengutak -atik algoritma dan menerapkan peningkatan kompatibilitas.
Sebagai catatan, ketika menguji skrip saya secara tidak sengaja meminta tweet, posting Facebook, dan artikel tertulis bahasa Inggris. Semuanya mendapat output yang dapat diterima, tetapi karena situs -situs itu bukan target saya menghapusnya dari daftar putih.
Setelah beberapa minggu umpan balik, saya memutuskan untuk menambahkan dukungan untuk bahasa Inggris. Ini membutuhkan sedikit refactoring.
Untuk membuatnya bekerja dengan bahasa lain, Anda hanya akan memerlukan file teks yang berisi semua kata berhenti dari bahasa tersebut dan menyalin beberapa baris kode (lihat Hapus bagian Kata -Kata Umum dan Stop).