Arabic (العربية/عربي) translation by Ayman Amar (you can also view the original English article)
مرحبا بك مرة أخرى في هذه السلسلة لإنشاء مستخرج الويب "web scraper". في هذا البرنامج التعليمي، سأبدأ من مثال على استخراج البيانات من موقع التدوين الخاص بي. سوف أشمل بالتفصيل كيف استخرجت البيانات، وكيفية المساعد والوسائل المساعدة على إنجاز وظائفهم، وكيفية تجميع كل قطع اللغز معا.
المواضيع
- استخراج التدوينة الخاصة بي
- النقب
- المستخرج
- طرق مساعدة
- كتابة المشاركات
استخراج التدوينة الخاصة بي
دعونا نضع ما تعلمناه حتى الآن في الممارسة العملية. لأسباب مختلفة، إعادة تصميم التدوينة الخاصة بي بين | شاشات كانت بالغة التأخير. كانت هناك مشاكل جعلتني أصرخ عندما استيقظت في الصباح. لذلك قررت إنشاء موقع ثابت جديد كليا، بواسطة Middleman واستضافة صفحات GitHub.
أنا استثمرت قدرا كبيرا من الوقت على التصميم الجديد بعد أن استعملت تدوينة Middleman لاحتياجاتي. كل ما بقي هو استيراد المحتوى من قاعدة البيانات الخاصة بي المخزنة على تطبيق Sinatra، لذلك أنا بحاجة إلى استخراج المحتوى الموجود ونقله إلى موقعي الثابت الجديد.
القيام بذلك يدويا في schmuck fashion لم يكن على الطاولة - ولا حتى سؤال - لأنني بإمكاني الإعتماد على أصدقائي نوكوجيري و مشانيز للقيام بهذه المهمة لأجلي. ما أمامك هو وظيفة استخراج صغيرة إلى حد معقول ليست معقدة جدا ولكن تقدم بعض التقلبات المثيرة للاهتمام التي ينبغي أن تكون تعليمية لمستجدين مستخرج الويب.
فيما يلي لقطتين من التدوينة الخاصة بي.
لقطة شاشة للتدوينة القديمة



لقطة شاشة للتدوينة الجديدة



دعونا نقوم بما نريد تحقيقه. نريد استخراج البيانات التالية من 139 حلقة موزعة على 21 موقع فهرس صفحات:
- العنوان
- الضيف
- الرؤوس الفرعية مع قائمة الموضوعات
- رقم مقطوعة SoundCloud لكل حلقة
- تاريخ
- الحلقة رقم
- النص من ملاحظات العرض
- الروابط من ملاحظات العرض
نحن أعدنا من خلال ترقيم الصفحات والسماح ل Mechanize بانقر فوق كل رابط للحلقة. في صفحة التفاصيل التالية، سوف نجد جميع المعلومات الواردة أعلاه التي نحتاج إليها. باستخدام هذه البيانات المستخرجة، ونحن نريد لملء المسألة الأمامية و "الجسم" من ملفات تخفيض السعر لكل حلقة.
في ما يلي يمكنك الاطلاع على معاينة لكيفية إنشاء ملفات ترحيل البيانات الجديدة باستخدام المحتوى الذي استخرجناه. وأعتقد أن هذا سيعطيك فكرة جيدة عن النطاق المقبل علينا. وهذا يمثل الخطوة الأخيرة في برمجتنا الصغيرة. لا تقلق، سوف ندخل في ذالك بمزيد من التفاصيل.
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
|
أردت أيضا إضافة بعض الحيل التي لم يتمكن الموقع القديم من القيام بها. امتلاك نظام علامات مخصص و شامل في مكان كان حاسم بالنسبة لي. أردت من المستمعين أن يكون لديهم أداة استكشاف عميق. لذلك، أنا بحاجة إلى علامات لكل ضيف وتقسيم الرؤوس الفرعية إلى العلامات أيضا. منذ أن أنتجت 139 حلقة في الموسم الأول وحده، واضطررت إلى إعداد الموقع لفترة لأن كمية المحتوى أصبح من الصعب فهرستها. نظام علامات عميق مع وضع ذكي للمستحسنة كان الطريق للذهاب. يسمح لي بالحفاظ على الموقع خفيف الوزن وسريع.
دعونا نلقي نظرة على رمز الكامل إستخراج المحتوى من موقعي. ننظر حولنا لمحاولة معرفة الصورة الكبيرة لما يجري. وبما أنني أتوقع أن تكون على جانب أشياء المبتدئين، بقيت بعيدا عن التلخيص أكثر من اللازم والخطأ على جانب الوضوح. قمت باثنين من إعادة بناء التعليمات البرمجية التي كانت تستهدف مساعدة وضوح الرمز، ولكن أيضا تركت قليلا من اللحوم على العظام لتبدأ بها مع عند الانتهاء من هذه المقالة. بعد كل شيء، جودة التعلم تحدث عندما تذهب بعد القراءة وتلعب مع بعض التعليمات البرمجية لوحدك.
على طول الطريق، أنا أشجعك بشدة لبدء التفكير في كيف يمكنك تحسين التعليمات البرمجية أمامك. ستكون هذه مهمتك النهائية في نهاية هذه المقالة. تلميح صغير مني: كسر طرق كبيرة في أصغر منها هو دائما نقطة انطلاق جيدة. بمجرد أن تفهم كيف يعمل رمز، سوف يكون لديك وقت ممتع لصقل اعادة بناء التعليمات البرمجية هذه.
لقد بدأت بالفعل من خلال استخراج مجموعة من الأساليب في مساعدات صغيرة مركزة. يجب أن تكون قادرا بسهولة على تطبيق ما تعلمته من مقالاتي السابقة عن روائح الرمز و اعادة بناء التعليمات البرمجية الخاصة بها. إذا كان هذا لا يزال يذهب من رأسك الآن، لا تقلق، جميعا كنا كذالك. إبقى هناك فقط، وعند نقطة معينة سوف تبدأ الأمور بالنقر أسرع.
الرمز كامل
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
|
لماذا لم نحتج إلى "Nokogiri"
؟ Mechanize يوفر لنا مع كل احتياجاتنا للإستخراج. كما ناقشنا في المقالة السابقة، Mechanize يبني فوق Nokogiri ويسمح لنا لاستخراج المحتوى كذلك. ومع ذلك، كان من المهم تغطية هذا الجوهر في المقالة الأولى منذ أن كنا بحاجة إلى بناء فوقها.
النقب
أول الأشياء أولاً. قبل أن نقفز إلى الرمز الخاص بنا هنا، اعتقدت أنه كان من الضروري أن أظهر لك كيف يمكنك التحقق بكفاءة إذا كان الرمز الخاص بك يعمل كما هو متوقع في كل خطوة على الطريق. كما لاحظت بالتأكيد، لقد أضفت أداة أخرى لهذا المزيج. من بين أمور أخرى، النقب
هو مفيد حقا لتصحيح الأخطاء.
إذا قمت بوضع Pry.start (ملزمة)
في أي مكان في الرمز الخاص بك، يمكنك فحص التطبيق الخاص بك في هذه النقطة بالضبط. يمكنك النقب في الأشياء في نقاط محددة في التطبيق. هذا مفيد حقا لاتخاذ التطبيق الخاص بك خطوة بخطوة دون التعثر. على سبيل المثال، دعنا نضعها مباشرة بعد وظيفة write_page
، ونعرف ما إذا كان الرابط
هو ما نتوقعه أم لا.
النقب
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 |
...
|
إذا قمت بتشغيل البرمجة، سوف تحصل على شيء من هذا القبيل.
الإخراج
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)> |
بعد ذالك عندما نطلب كائن الرابط
، يمكننا التحقق مما إذا كنا على الطريق الصحيح قبل أن ننتقل إلى تفاصيل التنفيذ الأخرى.
طرفية
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"> |
يبدو مثل ما نحتاجه. عظيم، يمكننا التحرك. القيام بهذه خطوة بخطوة من خلال التطبيق كله هو ممارسة هامة لضمان أنك لا تضيع وأنت حقا تفهم كيف يعمل. لا أريد أن أغطى النقب هنا بأي مزيد من التفصيل لأنه سوف يأخذني على الأقل لمقالة أخرى كاملة للقيام بذلك. يمكنني أن أوصي فقط باستخدامه كبديل قياسي ل IRB . العودة إلى مهمتنا الرئيسية.
المستخرج
الآن بعد أن كان لديك فرصة للتعرف على نفسك مع قطع اللغز في المكان، أوصي نذهب عليها واحدا تلو الآخر وتوضيح بضع نقاط مثيرة للاهتمام هنا وهناك. دعونا نبدأ مع القطع المركزية.
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 |
...
|
ماذا يحدث في طريقة الاستخراج
؟ أولا وقبل كل شيء، عقدت كل صفحة فهرس في المدونة القديمة. أنا أستخدم عنوان URL القديم من تطبيق Heroku بما أن الموقع الجديد هو بالفعل على الانترنت في betweenscreens.fm. كان لدي 20 صفحة من الحلقات التي كنت بحاجة إلى عقدها.
حددت العقدة عبر المتغير link_range
، الذي قمت بتحديثه مع كل عقدة. كانت عملية ترقيم الصفحات بسيطة مثل استخدام هذا المتغير في عنوان URL لكل صفحة. بسيطة وفعالة.
def scrape
1 |
page = agent.get("https://between-screens.herokuapp.com/?page=#{link_range}") |
ثم، كلما حصلت على صفحة جديدة مع ثماني حلقات أخرى للإستخراج، استخدم page.links
لتحديد الروابط التي نريد النقر عليها والمتابعة إلى صفحة التفاصيل لكل حلقة. قررت أن أستعمل مجموعة من الروابط (الروابط [2..8])
لأنها كانت متسقة في كل صفحة. وكانت أيضا أسهل طريقة لاستهداف الروابط التي أحتاج إليها من كل صفحة فهرس. لا حاجة للخطأ حولها مع محددات CSS هنا.
ثم نقوم بتغذية هذا الرابط لصفحة التفاصيل إلى أسلوب write_page
. هنا حيث معظم العمل قد تم. نأخذ هذا الرابط، انقر فوقه، واتبعه إلى صفحة التفاصيل حيث يمكننا البدء في استخراج بياناته. في هذه الصفحة، نجد جميع المعلومات التي أحتاج إليها لإنشاء حلقات التخفيض الجديد الخاصة بالموقع الجديد.
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
|
كما ترون أعلاه، نأخذdetails_page
وتطبيق مجموعة من أساليب الاستخراج على ذلك. نستخرج interviewee
, title
, sc_id
, text
, episode_title
و episode_number
. لقد قمت بإعادة بناء التعليمات البرمجية مجموعة من الأساليب التي تركز على المساعد والمسؤولة عن الاستخراج. دعونا نلقي نظرة سريعة عليها:
أساليب المساعد
أساليب الاستخراج
لقد استخرجت هذه المساعدات لأنها مكنتني من الحصول على أساليب أصغر عموما. كما أن إحتواء سلوكهم كان مهم أيضا. الرمز يقرأ أفضل كذلك. معظمهم يأخذ detail_page
كحجة ويستخرج بعض البيانات المحددة التي نحتاجها لمنشورات Middleman الخاصة بنا.
1 |
def extract_interviewee(detail_page) |
2 |
interviewee_selector = '.episode_sub_title span' |
3 |
detail_page.search(interviewee_selector).text.strip |
4 |
end
|
نحن نبحث في الصفحة على محدد خاص وللحصول على النص دون مساحة بيضاء لا حاجة لها.
1 |
def extract_title(detail_page) |
2 |
title_selector = ".episode_title" |
3 |
detail_page.search(title_selector).text.gsub(/[?#]/, '') |
4 |
end
|
نأخذ العنوان ونزيله ؟ و #
هذه لا تشتغل بشكل جيد مع المسألة الأمامية في المشاركات لحلقاتنا. المزيد من المعلومات حول المسألة الأمامية أدناه.
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
|
هنا نحن بحاجة إلى العمل أصعب قليلا لاستخراج معرف SoundCloud للمقطوعات المستضافة الخاصة بنا. أولا نحن بحاجة إلى جعل iframes ألية مع href
من soundcloud.com
وجعلها سلسلة للمسح ...
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]" |
ثم تطابق تعبيرا عاديا للأرقام من معرف القطعة الخاصة بنا soundcloud_id
"221003494"
.
1 |
def extract_shownotes_text(detail_page) |
2 |
shownote_selector = "#shownote_container > p" |
3 |
detail_page.search(shownote_selector) |
4 |
end
|
استخراج ملاحظات العرض مرة أخرى واضح تماما. نحن بحاجة فقط للبحث عن فقرات ملاحذات العرض في صفحة التفاصيل. لا مفاجآت هنا.
1 |
def extract_subtitle(detail_page) |
2 |
subheader_selector = ".episode_sub_title" |
3 |
detail_page.search(subheader_selector).text |
4 |
end
|
وينطبق الشيء نفسه على العنوان الفرعي، إلا أنه مجرد إعداد لاستخراج نظيف لرقم الحلقة منه.
1 |
def extract_episode_number(episode_subtitle) |
2 |
number = /[#]\d*/.match(episode_subtitle) |
3 |
clean_episode_number(number) |
4 |
end
|
هنا نحن بحاجة لجولة أخرى من التعبير العادي. دعونا نلقي نظرة قبل وبعد تطبيقنا للتعبير العادي.
episode_subtitle
1 |
" João Ferreira | 12 Minutes | Aug 26, 2015 | Episode #139 "
|
العدد
1 |
"#139"
|
خطوة واحدة أخرى حتى يصبح لدينا رقم نظيف.
1 |
def clean_episode_number(number) |
2 |
number.to_s.tr('#', '') |
3 |
end
|
نحن نأخذ هذا الرقم مع تجزئة #
ونزيلها. فوالا، لدينا أول رقم حلقة 139 مستخرجة كذلك. أقترح أن علينا أن ننظر إلى الوسائل المساعدة الأخرى قبل أن نجمع كل ذلك معا.
الوسائل المساعدة
بعد كل استخراج الأعمال هذا، علينا القيان ببعض التنظيف. يمكننا البدء بالفعل في إعداد البيانات لتكوين عملية التخفيض. على سبيل المثال، أنا قسمت episode_subtitle
قليلا أكثر للحصول على تاريخ نظيف وبناء العلامات
مع أسلوب 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
|
نشغل تعبير عادي آخر يبحث عن مواعيد مثل هذا: "26 أغسطس 2015"
. كما ترون، هذا ليس مفيدا جدا بعد. عن طريق string_date
نخرج من العنوان الفرعي، نحتاج إلى إنشاء كائن تاريخ
حقيقي. وإلا فإنه سيكون عديم الفائدة لإنشاء منشورات Middleman .
string_date
1 |
" Aug 26, 2015"
|
لذلك نأخذ هذه السلسلة للقيام ب Date.parse
. النتيجة تبدو واعدة أكثر.
التاريخ
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
|
هذه تأخد العنوان
والمقابلة
التي تراكمت لدينا داخل أسلوب extract_data
وتزيل كل الأحرف الأنابيب والخردة. نحن نستبدل أحرف الأنابيب بفاصلة، @
،؟
، #
، و &
بسلسلة فارغة، وأخيرا نهتم بالاختصارات ل 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
|
وفي النهاية، ندرج اسم الضيف في قائمة العلامات أيضا، والفصل بين العلامات بفاصلة.
قبل
1 |
"Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values"
|
بعد
1 |
"João Ferreira, Masters Work , Subvisual , Deadlines , Design personality , Design problems , Team , Pushing envelopes , Delightful experiences , Perfecting details , Company values"
|
ستؤدي كل واحدة من هذه العلامات إلى رابط لمجموعة من المشاركات لهذا الموضوع. يحدث كل هذا ضمن أسلوب extract_data
. دعونا نلقي نظرة أخرى أين نحن:
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
|
كل ما تبقى للقيام به هو العودة لتجزئة الخيارات مع البيانات التي استخرجناها. يمكننا تغذية هذه التجزئة في أسلوب compose_markdown
، والذي يجعل بياناتنا جاهزة لكونها مكتوبة كملف الذي أحتاجه لموقعي الجديد.
كتابة المشاركات
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
|
لنشر حلقات مدونة على موقع Middleman الخاص بي، اخترت إعادة استخدام نظام التدوين. بدلا من إنشاء مشاركات "نقية" في المدونة، أنشئ ملاحظات عرض لحلقاتي التي تعرض بإستضافة SoundCloud عبر iframes. على مواقع الفهرس، أعرض فقط iframe بالإضافة إلى العنوان والأدوات.
الصيغة التي أحتاجها لهذا العمل تتكون من شيء يسمى المادة الأمامية. هذا في الأساس مخزن ل مفتاح/قيمة لمواقعي الثابتة. هذا استبدال احتياجات قاعدة البيانات الخاصة بي من موقع Sinatra القديم الخاص بي.
البيانات مثل اسم الشخص الذي تمت مقابلته، والتاريخ،معرف SoundCloud للقصعة، ورقم الحلقة وما إلى ذالك يذهب بين ثلاث شرطات (---) على رأس ملفات الحلقة لدينا. في ما يلي محتوى كل حلقة - مثل الأسئلة والروابط والأدوات الراعية وما إلى ذلك.
المسألة الأمامية
1 |
--- |
2 |
key: value |
3 |
key: value |
4 |
key: value |
5 |
key: value |
6 |
--- |
7 |
|
8 |
Episode content goes here. |
في أسلوبcompose_markdown
، يمكنني استخدام HEREDOC
لإنشاء هذا الملف مع المسألة الأمامية لكل حلقة نلتف حولها. من تجزئة الخيارات سوف نقوم بتغذية هذا الأسلوب، سوف نستخراج كافة البيانات التي جمعناها في أسلوب المساعد 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 |
...
|
هذا هو المخطط لحلقة التدوينة جديدة هناك. هذا هو ما جئنا لأجله. ربما تتساءل عن هذا التركيب الخاص: #{options[:interviewee]}
. أنا أقتحم كالمعتاد مع السلاسل، ولكن بما أنني بالفعل بداخل <<-HEREDOC
، يمكن أن أترك مزدوجة اللإقتباس مفتوحة.
فقط لتوجيه أنفسنا، نحن ما زلنا في الحلقة، داخل وظيفة write_page
لكل رابط تم النقر عليه إلى صفحة التفاصيل مع ملاحظات العرض من حلقة مفردة. ما يحدث بعد ذلك إستعداد لكتابة هذا المخطط إلى نظام الملفات. وبعبارة أخرى، نقوم بإنشاء الحلقة الفعلية من خلال توفير اسم الملف و markdown_text
.
في هذه الخطوة النهائية، نحن بحاجة فقط لإعداد المكونات التالية: التاريخ، اسم الضيف، ورقم الحلقة. بالإضافة إلى markdown_text
بالتأكيد، الذي حصلنا عليه للتو من 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 |
...
|
ثم نحن بحاجة فقط إلى أخد file_name
و markdown_text
وكتابة الملف.
def write_page
1 |
...
|
2 |
|
3 |
File.open(file_name, 'w') { |file| file.write(markdown_text) } |
4 |
|
5 |
...
|
دعونا نكسر هذا أسفله أيضا. لكل مشاركة، أحتاج إلى تنسيق معين: شيء مثل 2016-10-25-Avdi-Grimm-120
. أريد أن أكتب الملفات التي تبدأ مع التاريخ وتشمل اسم الضيف ورقم الحلقة.
لتتناسب مع صيغة Middleman المتوقع للحصول على مشاركات جديدة، أنا بحاجة إلى أخذ اسم الضيف ووضعه من خلال أسلوب المساعد الخاص بي لتضييق
الاسم بالنسبة لي، من Avdi Grimm
إلى Avdi-Grimm
. لا شيء سحري، ولكن يستحق نظرة:
def dasherize
1 |
def dasherize(text) |
2 |
text.lstrip.rstrip.tr(' ', '-') |
3 |
end
|
هذا يزيل المسافة البيضاء من النص المستخرج لاسم الضيف ويحل محل المسافة البيضاء بين Avdi وGrimm مع تقطع. أما بقية اسم الملف فهي متقطعة معا في السلسلة نفسها: "date-interviewee-name-episodenumber".
def write_page
1 |
...
|
2 |
|
3 |
"#{date}-#{dasherize(interviewee)}-#{episode_number}.html.erb.md" |
4 |
|
5 |
...
|
وبما أن المحتوى المستخرج يأتي مباشرة من موقع HTML، لا يمكنني ببساطة استخدام .md
أو .markdown
كملحق لاسم الملف. قررت أن أختار .html.erb.md.
للحلقات المستقبلية التي أنشئ دون إستخراج، يمكنني ترك جزء .html.erb
وأحتاج فقط ل .md
.
بعد هذه الخطوة، تنتهي حلقة في وظيفة الإستخراج
، ويجب أن يكون لدينا حلقة واحدة التي تبدو هكذا:
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 |
هذا المستخرج سيبدأ في الحلقة الأخيرة، بالتأكيد، والحلقات حتى الأولى. لأغراض العرض التوضيحي، الحلقة 01 جيدة على أي حال. يمكنك أن ترى أعلى المسألة الأمامية مع البيانات التي استخرجناها.
كل ذلك كان مقفل سابقا في قاعدة البيانات الخاصة بي ل تطبيق Sinatra حلقة عدد والتاريخ واسم الضيف، وما إلى ذالك. الآن لدينا هذه جاهزة لتكون جزءا من موقع Middleman الجديد الثابت الخاص بي. كل شيء تحت الشرطتين الثلاثية (---) هو النص من ملاحظات العرض: الأسئلة والروابط غالبا.
افكار اخيرة
وقد إنتهينا. المدونة الجديدة الخاصة بي مشتغلة بالفعل . أنا سعيد حقا أخذت الوقت لإعادة تصميم الشيء من الألف إلى الياء. انها أفضل لنشر حلقات جديدة الآن. يجب أن يكون اكتشاف المحتوى الجديد أكثر سلاسة للمستخدمين أيضا.
كما ذكرت سابقا، وهذا هو الوقت الذي يجب أن تذهب إلى محرر التعليمات البرمجية للحصول على بعض المرح. اتخذ هذا الرمز وتصارع معه قليلا. حاول إيجاد طرق لجعله أكثر بساطة. هناك بعض الفرص لإعادة صياغة الرمز.
وعموما، آمل أن يكون هذا المثال الصغير أعطاك فكرة جيدة عن ما يمكنك القيام به مع مستخرج الويب الجديد الخاص بك. بالطبع يمكنك تحقيق تحديات أكثر تعقيدا بكثير، وأنا متأكد من أن هناك حتى الكثير من الفرص التجارية الصغيرة التي يمكن اتخاذها مع هذه المهارات.
ولكن كما هو الحال دائما، اتخدها خطوة واحدة في كل مرة، ولا تحبط إذا لم تقم بالأشياء على الفور. هذا فقط ليس طبيعيا بالنسبة لمعظم الناس لاكن متوقع. انها جزء من الرحلة. مستخرج سعيد!