Indonesian (Bahasa Indonesia) translation by Ilham Saputra (you can also view the original English article)
Selamat datang kembali ke seri ini untuk membuat scraper web. Dalam tutorial ini, saya akan melihat contoh dari data yang diambil dari situs podcast saya sendiri. Saya akan membahas secara terperinci bagaimana saya mengekstraksi data, bagaimana metode penolong dan utilitas menyelesaikan pekerjaan mereka, dan bagaimana semua kepingan puzzle bersatu.
Topik
- Scraping Podcast Saya
- Membongkar
- Scraper
- Metode Helper
- Menulis Posting
Scraping Podcast Saya
Mari letakkan apa yang telah kita pelajari sejauh ini dalam praktik. Karena berbagai alasan, desain ulang untuk podcast saya Antara | Layar sudah lama terlambat. Ada masalah yang membuat saya berteriak ketika saya bangun di pagi hari. Jadi saya memutuskan untuk membuat situs statis baru, yang dibangun bersama dengan Middleman dan dihosting dengan Halaman GitHub.
Saya menginvestasikan banyak waktu pada desain baru setelah saya mengubah blog seorang Middleman sesuai dengan kebutuhan saya. Yang tersisa adalah mengimpor konten dari aplikasi Sinatra yang didukung oleh database, jadi saya perlu mengikis konten yang ada dan mentransfernya ke situs statis baru saya.
Melakukan ini dengan tangan dalam mode goblok tidak ada di meja—bahkan bukan pertanyaan—karena aku bisa mengandalkan teman-temanku Nokogiri dan Mechanize untuk melakukan pekerjaan itu untukku. Apa yang ada di depan Anda adalah pekerjaan pengikisan yang cukup kecil yang tidak terlalu rumit tetapi menawarkan beberapa tikungan menarik yang harus mendidik bagi para pemula web yang mengorek di luar sana.
Di bawah ini adalah dua screenshot dari podcast saya.
Cuplikan Layar Podcast Lama



Cuplikan Layar Podcast Baru



Mari kita memecah apa yang ingin kita capai. Kami ingin mengekstrak data berikut dari 139 episode yang tersebar di 21 situs indeks paginasi:
- judul
- orang yang diwawancara
- subheader dengan daftar topik
- nomor trek SoundCloud untuk setiap episode
- tanggal
- nomor episode
- teks dari catatan acara
- tautan dari catatan acara
Kami melakukan iterasi melalui penomoran halaman dan biarkan Mekanize mengklik setiap tautan untuk sebuah episode. Pada halaman detail berikut, kita akan menemukan semua informasi dari atas yang kita butuhkan. Dengan menggunakan data yang tergores, kami ingin mengisi materi depan dan "body" dari file penurunan harga untuk setiap episode.
Di bawah ini Anda dapat melihat pratinjau tentang bagaimana kami akan menyusun file penurunan harga baru dengan konten yang kami ekstrak. Saya pikir ini akan memberi Anda ide yang baik tentang ruang lingkup di depan kita. Ini merupakan langkah terakhir dalam skrip kecil kami. Jangan khawatir, kami akan membahasnya lebih detail.
def compose_markdown
1 |
def compose_markdown(options={}) |
2 |
<<-HEREDOC |
3 |
---
|
4 |
title: #{options[:interviewee]} |
5 |
interviewee: #{options[:interviewee]} |
6 |
topic_list: #{options[:title]} |
7 |
tags: #{options[:tags]} |
8 |
soundcloud_id: #{options[:sc_id]} |
9 |
date: #{options[:date]} |
10 |
episode_number: #{options[:episode_number]} |
11 |
---
|
12 |
|
13 |
#{options[:text]} |
14 |
HEREDOC
|
15 |
end
|
Saya juga ingin menambahkan beberapa trik yang tidak bisa dimainkan oleh situs lama. Memiliki sistem penandaan yang menyeluruh dan komprehensif adalah sangat penting bagi saya. Saya ingin pendengar memiliki alat penemuan yang mendalam. Oleh karena itu, saya membutuhkan tag untuk setiap orang yang diwawancara dan membagi subheader ke dalam tag juga. Karena saya menghasilkan 139 episode di musim pertama saja, saya harus menyiapkan situs untuk waktu ketika jumlah konten menjadi lebih sulit untuk disisir. Sistem penandaan yang mendalam dengan rekomendasi yang ditempatkan cerdas adalah cara untuk pergi. Ini memungkinkan saya untuk menjaga situs ringan dan cepat.
Mari kita lihat kode lengkap untuk menggores konten situs saya. Lihatlah ke sekeliling dan coba cari gambaran besar tentang apa yang sedang terjadi. Karena saya mengharapkan Anda berada di sisi pemula hal-hal, saya menjauhkan diri dari terlalu banyak mengaburkan dan keliru pada sisi kejelasan. Saya melakukan beberapa refactorings yang ditargetkan pada kode bantuan kejelasan, tapi saya juga meninggalkan sedikit daging di tulang untuk Anda bermain dengan ketika Anda selesai dengan artikel ini. Setelah semua, kualitas pembelajaran terjadi ketika Anda melampaui membaca dan main-main dengan beberapa kode pada Anda sendiri.
Sepanjang perjalanan, saya sangat mendorong Anda untuk mulai berpikir tentang bagaimana Anda dapat memperbaiki kode di depan Anda. Ini akan menjadi tugas akhir Anda di bagian akhir artikel ini. Sedikit petunjuk dari saya: memecah metode besar menjadi metode yang lebih kecil selalu menjadi titik awal yang baik. Setelah Anda memahami bagaimana kode bekerja, Anda harus memiliki waktu yang menyenangkan dalam mengasah refactoring tersebut.
Saya sudah mulai dengan mengekstraksi banyak metode menjadi pembantu yang kecil dan terfokus. Anda harus dengan mudah dapat menerapkan apa yang telah Anda pelajari dari artikel saya sebelumnya tentang code smells dan refactorings mereka. Jika ini masih di atas kepala Anda sekarang, jangan khawatir—kami semua sudah ada di sana. Teruslah melakukannya, dan pada beberapa titik hal-hal akan mulai mengklik lebih cepat.
Kode Lengkap
1 |
require 'Mechanize' |
2 |
require 'Pry' |
3 |
require 'date' |
4 |
|
5 |
# Helper Methods
|
6 |
|
7 |
# (Extraction Methods)
|
8 |
|
9 |
def extract_interviewee(detail_page) |
10 |
interviewee_selector = '.episode_sub_title span' |
11 |
detail_page.search(interviewee_selector).text.strip |
12 |
end
|
13 |
|
14 |
def extract_title(detail_page) |
15 |
title_selector = ".episode_title" |
16 |
detail_page.search(title_selector).text.gsub(/[?#]/, '') |
17 |
end
|
18 |
|
19 |
def extract_soundcloud_id(detail_page) |
20 |
sc = detail_page.iframes_with(href: /soundcloud.com/).to_s |
21 |
sc.scan(/\d{3,}/).first |
22 |
end
|
23 |
|
24 |
def extract_shownotes_text(detail_page) |
25 |
shownote_selector = "#shownote_container > p" |
26 |
detail_page.search(shownote_selector) |
27 |
end
|
28 |
|
29 |
def extract_subtitle(detail_page) |
30 |
subheader_selector = ".episode_sub_title" |
31 |
detail_page.search(subheader_selector).text |
32 |
end
|
33 |
|
34 |
def extract_episode_number(episode_subtitle) |
35 |
number = /[#]\d*/.match(episode_subtitle) |
36 |
clean_episode_number(number) |
37 |
end
|
38 |
|
39 |
# (Utility Methods)
|
40 |
|
41 |
def clean_date(episode_subtitle) |
42 |
string_date = /[^|]*([,])(.....)/.match(episode_subtitle).to_s |
43 |
Date.parse(string_date) |
44 |
end
|
45 |
|
46 |
def build_tags(title, interviewee) |
47 |
extracted_tags = strip_pipes(title) |
48 |
"#{interviewee}"+ ", #{extracted_tags}" |
49 |
end
|
50 |
|
51 |
def strip_pipes(text) |
52 |
tags = text.tr('|', ',') |
53 |
tags = tags.gsub(/[@?#&]/, '') |
54 |
tags.gsub(/[w\/]{2}/, 'with') |
55 |
end
|
56 |
|
57 |
def clean_episode_number(number) |
58 |
number.to_s.tr('#', '') |
59 |
end
|
60 |
|
61 |
def dasherize(text) |
62 |
text.lstrip.rstrip.tr(' ', '-') |
63 |
end
|
64 |
|
65 |
def extract_data(detail_page) |
66 |
interviewee = extract_interviewee(detail_page) |
67 |
title = extract_title(detail_page) |
68 |
sc_id = extract_soundcloud_id(detail_page) |
69 |
text = extract_shownotes_text(detail_page) |
70 |
episode_subtitle = extract_subtitle(detail_page) |
71 |
episode_number = extract_episode_number(episode_subtitle) |
72 |
date = clean_date(episode_subtitle) |
73 |
tags = build_tags(title, interviewee) |
74 |
|
75 |
options = { |
76 |
interviewee: interviewee, |
77 |
title: title, |
78 |
sc_id: sc_id, |
79 |
text: text, |
80 |
tags: tags, |
81 |
date: date, |
82 |
episode_number: episode_number |
83 |
}
|
84 |
end
|
85 |
|
86 |
def compose_markdown(options={}) |
87 |
<<-HEREDOC |
88 |
---
|
89 |
title: #{options[:interviewee]} |
90 |
interviewee: #{options[:interviewee]} |
91 |
topic_list: #{options[:title]} |
92 |
tags: #{options[:tags]} |
93 |
soundcloud_id: #{options[:sc_id]} |
94 |
date: #{options[:date]} |
95 |
episode_number: #{options[:episode_number]} |
96 |
---
|
97 |
|
98 |
#{options[:text]} |
99 |
HEREDOC
|
100 |
end
|
101 |
|
102 |
def write_page(link) |
103 |
detail_page = link.click |
104 |
|
105 |
extracted_data = extract_data(detail_page) |
106 |
|
107 |
markdown_text = compose_markdown(extracted_data) |
108 |
date = extracted_data[:date] |
109 |
interviewee = extracted_data[:interviewee] |
110 |
episode_number = extracted_data[:episode_number] |
111 |
|
112 |
File.open("#{date}-#{dasherize(interviewee)}-#{episode_number}.html.erb.md", 'w') { |file| file.write(markdown_text) } |
113 |
end
|
114 |
|
115 |
def scrape |
116 |
link_range = 1 |
117 |
agent ||= Mechanize.new |
118 |
|
119 |
until link_range == 21 |
120 |
page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
121 |
link_range += 1 |
122 |
|
123 |
page.links[2..8].map do |link| |
124 |
write_page(link) |
125 |
end
|
126 |
end
|
127 |
end
|
128 |
|
129 |
scrape
|
Mengapa kita tidak require "Nokogiri"
? Mekanize memberi kita semua kebutuhan pengikisan kita. Seperti yang telah kita bahas pada artikel sebelumnya, Mechanize dibangun di atas Nokogiri dan memungkinkan kita untuk mengekstrak konten juga. Itu, bagaimanapun, penting untuk menutupi permata itu di artikel pertama karena kami perlu membangun di atasnya.
Membongkar
Hal pertama yang pertama. Sebelum kami melompat ke kode kami di sini, saya pikir perlu untuk menunjukkan kepada Anda bagaimana Anda dapat secara efisien memeriksa apakah kode Anda berfungsi seperti yang diharapkan setiap langkah. Seperti yang Anda perhatikan, saya telah menambahkan alat lain ke dalam campuran. Di antaranya, Pry
sangat berguna untuk debugging.
Jika Anda menempatkan Pry.start(binding)
di mana saja dalam kode Anda, Anda dapat memeriksa aplikasi Anda tepat pada saat itu. Anda dapat mengorek objek pada titik-titik tertentu dalam aplikasi. Ini sangat membantu untuk mengambil aplikasi Anda selangkah demi selangkah tanpa tersandung kaki Anda sendiri. Misalnya, mari letakkan tepat setelah fungsi write_page
dan periksa apakah link
adalah yang kita harapkan.
Membongkar
1 |
...
|
2 |
|
3 |
def scrape |
4 |
link_range = 1 |
5 |
agent ||= Mechanize.new |
6 |
|
7 |
until link_range == 21 |
8 |
page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
9 |
link_range += 1 |
10 |
|
11 |
page.links[2..8].map do |link| |
12 |
write_page(link) |
13 |
Pry.start(binding) |
14 |
end
|
15 |
end
|
16 |
end
|
17 |
|
18 |
...
|
Jika Anda menjalankan skripnya, kita akan mendapatkan sesuatu seperti ini.
Output
1 |
»$ ruby noko_scraper.rb |
2 |
|
3 |
321: def scrape |
4 |
322: link_range = 1 |
5 |
323: agent ||= Mechanize.new |
6 |
324: |
7 |
326: until link_range == 21 |
8 |
327: page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
9 |
328: link_range += 1 |
10 |
329: |
11 |
330: page.links[2..8].map do |link| |
12 |
331: write_page(link) |
13 |
=> 332: Pry.start(binding) |
14 |
333: end |
15 |
334: end |
16 |
335: end |
17 |
|
18 |
[1] pry(main)> |
Saat kami meminta objek link
, kami dapat memeriksa apakah kami berada di jalur yang benar sebelum kami melanjutkan ke detail penerapan lainnya.
Terminal
1 |
[2] pry(main)> link |
2 |
=> #<Mechanize::Page::Link |
3 |
"Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values"
|
4 |
"/episodes/139"> |
Terlihat seperti apa yang kita butuhkan. Hebat, kita bisa melanjutkan. Melakukan langkah demi langkah melalui seluruh aplikasi ini merupakan praktik penting untuk memastikan bahwa Anda tidak tersesat dan Anda benar-benar memahami cara kerjanya. Saya tidak akan membahas Pry di sini secara lebih terperinci karena akan membutuhkan setidaknya artikel lengkap untuk melakukannya. Saya hanya dapat merekomendasikan menggunakannya sebagai alternatif untuk shell IRB standar. Kembali ke tugas utama kita.
Scraper
Sekarang setelah Anda memiliki kesempatan untuk membiasakan diri dengan potongan-potongan teka-teki di tempat, saya sarankan kita membahas satu per satu dan mengklarifikasi beberapa poin menarik di sana-sini. Mari mulai dengan potongan pusat.
podcast_scraper.rb
1 |
...
|
2 |
|
3 |
def write_page(link) |
4 |
detail_page = link.click |
5 |
|
6 |
extracted_data = extract_data(detail_page) |
7 |
|
8 |
markdown_text = compose_markdown(extracted_data) |
9 |
date = extracted_data[:date] |
10 |
interviewee = extracted_data[:interviewee] |
11 |
episode_number = extracted_data[:episode_number] |
12 |
|
13 |
file_name = "#{date}-#{dasherize(interviewee)}-#{episode_number}.html.erb.md" |
14 |
|
15 |
File.open(file_name, 'w') { |file| file.write(markdown_text) } |
16 |
end
|
17 |
|
18 |
def scrape |
19 |
link_range = 1 |
20 |
agent ||= Mechanize.new |
21 |
|
22 |
until link_range == 21 |
23 |
page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
24 |
link_range += 1 |
25 |
|
26 |
page.links[2..8].map do |link| |
27 |
write_page(link) |
28 |
end
|
29 |
end
|
30 |
end
|
31 |
|
32 |
...
|
Apa yang terjadi dalam metode scrape
? Pertama-tama, saya mengulang setiap halaman indeks di podcast lama. Saya menggunakan URL lama dari aplikasi Heroku karena situs baru sudah online di betweenscreens.fm. Saya memiliki 20 halaman episode yang perlu saya lewati.
Saya membatasi pengulangan melalui variabel link_range
, yang saya perbarui dengan setiap loop. Akan melalui pagination itu sesederhana menggunakan variabel ini di URL setiap halaman. Sederhana dan efektif.
def scrape
1 |
page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
Kemudian, setiap kali saya mendapat halaman baru dengan delapan episode lain untuk mengikis, saya menggunakan page.links
untuk mengidentifikasi tautan yang ingin kami klik dan ikuti ke halaman detail untuk setiap episode. Saya memutuskan untuk menggunakan berbagai tautan (links[2..8]
) karena konsisten di setiap halaman. Itu juga cara termudah untuk menargetkan tautan yang saya perlukan dari setiap halaman indeks. Tidak perlu meraba-raba dengan pemilih CSS di sini.
Kami kemudian memberi makan tautan itu untuk halaman detail ke metode write_page
. Di sinilah sebagian besar pekerjaan dilakukan. Kami mengambil tautan itu, mengekliknya, dan mengikutinya ke laman detail tempat kami dapat mulai mengekstrak datanya. Di halaman itu kami menemukan semua informasi yang saya perlukan untuk menyusun episode penurunan harga baru untuk situs baru.
def write_page
1 |
extracted_data = extract_data(detail_page) |
def extract_data
1 |
def extract_data(detail_page) |
2 |
interviewee = extract_interviewee(detail_page) |
3 |
title = extract_title(detail_page) |
4 |
sc_id = extract_soundcloud_id(detail_page) |
5 |
text = extract_shownotes_text(detail_page) |
6 |
episode_subtitle = extract_subtitle(detail_page) |
7 |
episode_number = extract_episode_number(episode_subtitle) |
8 |
date = clean_date(episode_subtitle) |
9 |
tags = build_tags(title, interviewee) |
10 |
|
11 |
options = { |
12 |
interviewee: interviewee, |
13 |
title: title, |
14 |
sc_id: sc_id, |
15 |
text: text, |
16 |
tags: tags, |
17 |
date: date, |
18 |
episode_number: episode_number |
19 |
}
|
20 |
end
|
Seperti yang Anda lihat di atas, kami mengambil detail_page
dan menerapkan banyak metode ekstraksi di atasnya. Kami mengekstrak orang yang interviewee
, title
, sc_id
, text
, episode_title
, dan episode_number
. Saya mem-refactor sekelompok metode helper terfokus yang bertanggung jawab atas tanggung jawab ekstraksi ini. Mari kita lihat cepat mereka:
Metode Helper
Metode Extraction
Saya mengekstrak para penolong ini karena itu memungkinkan saya untuk memiliki metode yang lebih kecil secara keseluruhan. Mengenkapsulasi perilaku mereka juga penting. Kode juga dibaca lebih baik. Sebagian besar dari mereka mengambil detail_page
sebagai argumen dan mengekstrak beberapa data spesifik yang kami perlukan untuk posting Middleman kami.
1 |
def extract_interviewee(detail_page) |
2 |
interviewee_selector = '.episode_sub_title span' |
3 |
detail_page.search(interviewee_selector).text.strip |
4 |
end
|
Kami mencari halaman untuk pemilih tertentu dan mendapatkan teks tanpa ruang putih yang tidak perlu.
1 |
def extract_title(detail_page) |
2 |
title_selector = ".episode_title" |
3 |
detail_page.search(title_selector).text.gsub(/[?#]/, '') |
4 |
end
|
Kami mengambil judul dan menghapus ?
dan #
karena ini tidak bermain bagus dengan materi depan di postingan untuk episode kami. Lebih lanjut tentang materi depan di bawah ini.
1 |
def extract_soundcloud_id(detail_page) |
2 |
sc = detail_page.iframes_with(href: /soundcloud.com/).to_s |
3 |
sc.scan(/\d{3,}/).first |
4 |
end
|
Di sini kami perlu bekerja sedikit lebih keras untuk mengekstrak id SoundCloud untuk trek yang kami hosting. Pertama kita perlu Mekanize iframes dengan href
dari soundcloud.com
dan menjadikannya string untuk pemindaian...
1 |
"[#<Mechanize::Page::Frame\n nil\n \"https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/221003494&auto_play=false&hide_related=false&show_comments=false&show_user=true&show_reposts=false&visual=true\">\n]" |
Kemudian cocokkan ekspresi reguler untuk digit-digitnya dari id trek—soundcloud_id
"221003494"
kami.
1 |
def extract_shownotes_text(detail_page) |
2 |
shownote_selector = "#shownote_container > p" |
3 |
detail_page.search(shownote_selector) |
4 |
end
|
Mengekstrak menunjukkan catatan lagi cukup mudah. Kita hanya perlu mencari catatan menunjukkan paragraf di halaman detail. Tidak ada kejutan di sini.
1 |
def extract_subtitle(detail_page) |
2 |
subheader_selector = ".episode_sub_title" |
3 |
detail_page.search(subheader_selector).text |
4 |
end
|
Sama berlaku untuk subjudul, kecuali bahwa itu hanya persiapan untuk bersih ekstrak nomor episode dari itu.
1 |
def extract_episode_number(episode_subtitle) |
2 |
number = /[#]\d*/.match(episode_subtitle) |
3 |
clean_episode_number(number) |
4 |
end
|
Di sini kita membutuhkan ronde lain dari ekspresi reguler. Mari kita lihat sebelum dan sesudah kami menerapkan regex.
episode_subtitle
1 |
" João Ferreira | 12 Minutes | Aug 26, 2015 | Episode #139 "
|
number
1 |
"#139"
|
Satu langkah lagi sampai kita memiliki nomor yang bersih.
1 |
def clean_episode_number(number) |
2 |
number.to_s.tr('#', '') |
3 |
end
|
Kami mengambil nomor itu dengan hash #
dan menghapusnya. Voilà, kami memiliki nomor episode 139
pertama kami yang diekstraksi juga. Saya menyarankan agar kita melihat metode utilitas lain juga sebelum kita menyatukannya.
Metode Utility
Setelah semua bisnis ekstraksi itu, kami memiliki beberapa pembersihan yang harus dilakukan. Kita sudah bisa mulai menyiapkan data untuk menyusun penurunan harga. Sebagai contoh, saya mengiris episode_subtitle
lagi untuk mendapatkan tanggal yang bersih dan membangun tags
dengan metode build_tags
.
def clean_date
1 |
def clean_date(episode_subtitle) |
2 |
string_date = /[^|]*([,])(.....)/.match(episode_subtitle).to_s |
3 |
Date.parse(string_date) |
4 |
end
|
Kami menjalankan regex lain yang mencari tanggal seperti ini: " Aug 26, 2015"
. Seperti yang Anda lihat, ini belum sangat membantu. Dari string_date
yang kita dapatkan dari subtitle, kita perlu membuat objek Date
yang nyata. Kalau tidak, akan sia-sia untuk membuat posting Middleman.
string_date
1 |
" Aug 26, 2015"
|
Oleh karena itu kami mengambil string itu dan melakukan Date.parse
. Hasilnya terlihat jauh lebih menjanjikan.
Tanggal
1 |
2015-08-26 |
def build_tags
1 |
def build_tags(title, interviewee) |
2 |
extracted_tags = strip_pipes(title) |
3 |
"#{interviewee}"+ ", #{extracted_tags}" |
4 |
end
|
Ini mengambil title
dan interviewee
kami telah membangun di dalam metode extract_data
dan menghapus semua karakter pipa dan sampah. Kami mengganti karakter pipa dengan koma, @
, ?
, #
, dan &
dengan string kosong, dan akhirnya menjaga singkatan with
.
def strip_pipes
1 |
def strip_pipes(text) |
2 |
tags = text.tr('|', ',') |
3 |
tags = tags.gsub(/[@?#&]/, '') |
4 |
tags.gsub(/[w\/]{2}/, 'with') |
5 |
end
|
Pada akhirnya kami memasukkan nama yang diwawancarai dalam daftar tag juga, dan memisahkan setiap tag dengan koma.
Before
1 |
"Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values"
|
After
1 |
"João Ferreira, Masters Work , Subvisual , Deadlines , Design personality , Design problems , Team , Pushing envelopes , Delightful experiences , Perfecting details , Company values"
|
Setiap tag ini akan menjadi tautan ke koleksi pos untuk topik tersebut. Semua ini terjadi di dalam metode extract_data
. Mari kita telah melihat di mana kita berada:
def extract_data
1 |
def extract_data(detail_page) |
2 |
interviewee = extract_interviewee(detail_page) |
3 |
title = extract_title(detail_page) |
4 |
sc_id = extract_soundcloud_id(detail_page) |
5 |
text = extract_shownotes_text(detail_page) |
6 |
episode_subtitle = extract_subtitle(detail_page) |
7 |
episode_number = extract_episode_number(episode_subtitle) |
8 |
date = clean_date(episode_subtitle) |
9 |
tags = build_tags(title, interviewee) |
10 |
|
11 |
options = { |
12 |
interviewee: interviewee, |
13 |
title: title, |
14 |
sc_id: sc_id, |
15 |
text: text, |
16 |
tags: tags, |
17 |
date: date, |
18 |
episode_number: episode_number |
19 |
}
|
20 |
end
|
Semua yang tersisa untuk dilakukan di sini adalah mengembalikan opsi hash dengan data yang kami ekstrak. Kita dapat memberi makan hash ini ke dalam metode compose_markdown
, yang membuat data kami siap untuk ditulis sebagai file yang saya perlukan untuk situs baru saya.
Menulis Posting
def compose_markdown
1 |
def compose_markdown(options={}) |
2 |
<<-HEREDOC |
3 |
---
|
4 |
title: #{options[:interviewee]} |
5 |
interviewee: #{options[:interviewee]} |
6 |
topic_list: #{options[:title]} |
7 |
tags: #{options[:tags]} |
8 |
soundcloud_id: #{options[:sc_id]} |
9 |
date: #{options[:date]} |
10 |
episode_number: #{options[:episode_number]} |
11 |
---
|
12 |
|
13 |
#{options[:text]} |
14 |
HEREDOC
|
15 |
end
|
Untuk mempublikasikan episode podcast di situs Middleman saya, saya memilih untuk menggunakan kembali sistem blognya. Alih-alih membuat tulisan blog "pure", saya membuat catatan acara untuk episode saya yang menampilkan episode yang dihosting SoundCloud melalui iframes. Di situs indeks, saya hanya menampilkan iframe itu plus judul dan lainnya.
Format yang saya butuhkan untuk bekerja ini terdiri dari sesuatu yang disebut materi depan. Ini pada dasarnya adalah toko kunci/nilai untuk situs statis saya. Ini menggantikan kebutuhan database saya dari situs Sinatra lama saya.
Data seperti nama yang diwawancarai, tanggal, ID trek SoundCloud, nomor episode, dan seterusnya masuk di antara tiga tanda garis (---
) di atas file episode kami. Di bawah ini muncul konten untuk setiap episode—hal-hal seperti pertanyaan, tautan, hal sponsor, dll.
Front Matter
1 |
--- |
2 |
key: value |
3 |
key: value |
4 |
key: value |
5 |
key: value |
6 |
--- |
7 |
|
8 |
Episode content goes here. |
Dalam metode compose_markdown
, saya menggunakan HEREDOC
untuk menyusun file dengan materi depan untuk setiap episode yang kami lewati. Dari opsi hash kami memberi makan metode ini, kami mengekstrak semua data yang kami kumpulkan dalam metode helper extract_data
.
def compose_markdown
1 |
...
|
2 |
|
3 |
<<-HEREDOC |
4 |
---
|
5 |
title: #{options[:interviewee]} |
6 |
interviewee: #{options[:interviewee]} |
7 |
topic_list: #{options[:title]} |
8 |
tags: #{options[:tags]} |
9 |
soundcloud_id: #{options[:sc_id]} |
10 |
date: #{options[:date]} |
11 |
episode_number: #{options[:episode_number]} |
12 |
---
|
13 |
|
14 |
#{options[:text]} |
15 |
HEREDOC
|
16 |
|
17 |
...
|
Ini adalah cetak biru untuk episode podcast baru di sana. Ini adalah tujuan kami. Mungkin Anda bertanya-tanya tentang sintaks khusus ini: #{options[:interviewee]}
. Saya melakukan interpolasi seperti biasa dengan string, tetapi karena saya sudah berada di dalam <<-HEREDOC
, saya dapat meninggalkan tanda kutip ganda.
Hanya untuk mengorientasikan diri, kita masih dalam pengulangan, di dalam fungsi write_page
untuk setiap tautan yang diklik ke halaman detail dengan catatan acara dari episode tunggal. Apa yang terjadi selanjutnya adalah mempersiapkan untuk menulis cetak biru ini ke sistem file. Dengan kata lain, kami membuat episode sebenarnya dengan memberikan nama file dan markdown_text
yang dibuat.
Untuk langkah terakhir, kita hanya perlu menyiapkan bahan-bahan berikut: tanggal, nama orang yang diwawancara, dan nomor episode. Selain itu, markdown_text
tentu saja, yang baru kami dapatkan dari compose_markdown
.
def write_page
1 |
...
|
2 |
|
3 |
markdown_text = compose_markdown(extracted_data) |
4 |
date = extracted_data[:date] |
5 |
interviewee = extracted_data[:interviewee] |
6 |
episode_number = extracted_data[:episode_number] |
7 |
|
8 |
file_name = "#{date}-#{dasherize(interviewee)}-#{episode_number}.html.erb.md" |
9 |
|
10 |
...
|
Maka kita hanya perlu mengambil file_name
dan markdown_text
dan menulis file.
def write_page
1 |
...
|
2 |
|
3 |
File.open(file_name, 'w') { |file| file.write(markdown_text) } |
4 |
|
5 |
...
|
Mari kita hancurkan ini juga. Untuk setiap posting, saya membutuhkan format tertentu: sesuatu seperti 2016-10-25-Avdi-Grimm-120
. Saya ingin menulis file yang dimulai dengan tanggal dan menyertakan nama yang diwawancara dan nomor episode.
Untuk mencocokkan format Middleman mengharapkan untuk posting baru, saya perlu mengambil nama yang diwawancarai dan memasukkannya melalui metode helper saya untuk dasherize
nama untuk saya, dari Avdi Grimm
ke Avdi-Grimm
. Tidak ada yang ajaib, tapi layak dilihat:
def dasherize
1 |
def dasherize(text) |
2 |
text.lstrip.rstrip.tr(' ', '-') |
3 |
end
|
Ini menghapus spasi dari teks yang kita gesek untuk nama yang diwawancarai dan menggantikan ruang putih antara Avdi dan Grimm dengan tanda hubung. Sisa nama file diputuskan bersama-sama dalam string itu sendiri: "date-interviewee-name-episodenumber"
.
def write_page
1 |
...
|
2 |
|
3 |
"#{date}-#{dasherize(interviewee)}-#{episode_number}.html.erb.md" |
4 |
|
5 |
...
|
Karena isi diekstraksi berasal langsung dari situs HTML, saya hanya tidak dapat menggunakan .md
atau .markdown
sebagai ekstensi nama file. Saya memutuskan untuk menggunakan .html.erb.md
. Untuk episode mendatang yang saya tulis tanpa tergores, saya dapat meninggalkan bagian .html.erb
dan hanya perlu .md
.
Setelah langkah ini, loop dalam fungsi scrape
berakhir, dan kita harus memiliki satu episode yang terlihat seperti ini:
2014-12-01-Avdi-Grimm-1.html.erb.md
1 |
--- |
2 |
title: Avdi Grimm |
3 |
interviewee: Avdi Grimm |
4 |
topic_list: What is Rake | Origins | Jim Weirich | Common use cases | Advantages of Rake |
5 |
tags: Avdi Grimm, What is Rake , Origins , Jim Weirich , Common use cases , Advantages of Rake |
6 |
soundcloud_id: 179619755 |
7 |
date: 2014-12-01 |
8 |
episode_number: 1 |
9 |
--- |
10 |
|
11 |
Questions: |
12 |
- What is Rake? |
13 |
- What can you tell us about the origins of Rake? |
14 |
- What can you tell us about Jim Weihrich? |
15 |
- What are the most common use cases for Rake? |
16 |
- What are the most notable advantages of Rake? |
17 |
|
18 |
Links: |
19 |
In">http://www.youtube.com/watch?v=2ZHJSrF52bc">In memory of the great Jim Weirich |
20 |
Rake">https://github.com/jimweirich/rake">Rake on GitHub |
21 |
Jim">https://github.com/jimweirich">Jim Weirich on GitHub |
22 |
Basic">http://www.youtube.com/watch?v=AFPWDzHWjEY">Basic Rake talk by Jim Weirich |
23 |
Power">http://www.youtube.com/watch?v=KaEqZtulOus">Power Rake talk by Jim Weirich |
24 |
Learn">http://devblog.avdi.org/2014/04/30/learn-advanced-rake-in-7-episodes/">Learn advanced Rake in 7 episodes - from Avdi Grimm ( free ) |
25 |
Avdi">http://about.avdi.org/">Avdi Grimm |
26 |
Avdi Grimm’s screencasts: Ruby">http://www.rubytapas.com/">Ruby Tapas |
27 |
Ruby">http://devchat.tv/ruby-rogues/">Ruby Rogues podcast with Avdi Grimm |
28 |
Great ebook: Rake">http://www.amazon.com/Rake-Management-Essentials-Andrey-Koleshko/dp/1783280778">Rake Task Management Essentials fromhttps://twitter.com/ka8725"> Andrey Koleshko |
Scraper ini akan mulai pada episode terakhir, tentu saja, dan loop sampai yang pertama. Untuk tujuan demonstrasi, episode 01 sebagus apa pun. Anda dapat melihat di bagian atas masalah depan dengan data yang kami ekstrak.
Semua itu sebelumnya terkunci dalam database aplikasi Sinatra saya—nomor episode, tanggal, nama orang yang diwawancara, dan seterusnya. Sekarang kami siap untuk menjadi bagian dari situs baru saya di Harvard Middleman. Segala sesuatu di bawah dua garis tiga (---
) adalah teks dari catatan acara: pertanyaan dan tautan sebagian besar.
Pikiran Akhir
Dan kita selesai. Podcast baru saya sudah aktif dan berjalan. Saya benar-benar senang saya meluangkan waktu untuk mendesain ulang sesuatu dari bawah ke atas. Jauh lebih keren untuk mempublikasikan episode baru sekarang. Menemukan konten baru harus halus untuk pengguna juga.
Seperti yang saya sebutkan sebelumnya, ini adalah waktu dimana Anda harus pergi ke editor kode Anda untuk memiliki beberapa menyenangkan. Ambil kode ini dan bergumul dengan itu sedikit. Mencoba untuk menemukan cara untuk membuatnya sederhana. Ada beberapa kesempatan untuk refactor kode.
Secara keseluruhan, saya berharap contoh kecil ini memberi Anda ide yang baik dari apa yang dapat Anda lakukan dengan web baru scraping daging. Tentu saja Anda dapat mencapai tantangan yang jauh lebih canggih—saya yakin ada banyak peluang bisnis kecil yang harus dibuat dengan keterampilan ini.
Tapi seperti biasa, mengambil satu langkah pada satu waktu, dan jangan terlalu frustrasi jika hal-hal tidak mengklik segera. Hal ini tidak hanya normal bagi kebanyakan orang tetapi untuk diharapkan. Itu bagian dari perjalanan. Happy scraping!