הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

הדלס מי?

08/02/2019

פיצ'ר ריצה ללא ראש של כרום עשוי לשנות את המשחק בכל מה שקשור ל Web Scraping. בזכות פיצ'ר זה אתם יכולים להפעיל כרום ממש מתוך תוכנית פייתון, לגלוש לאתרים, ללחוץ על כפתורים שעל המסך וכל זה בלי שתצטרכו ממשק משתמש גרפי מה שאומר שאתם יכולים להריץ את הקוד גם מסקריפט אוטומטי שרץ על השרת.

בואו נצא לסיבוב עם כרום נטול הראש כדי לראות איך הוא מתמודד עם העולם.

תחילה התקנת הכלים- תצטרכו להתקין את כרום זה ברור ואני מניח שיש לכם פייתון מותקן (למרות שממש לא חייבים פייתון ואפשר לכתוב את כל דוגמאות הקוד כאן גם בכל שפה אחרת שתומכת בסלניום). בכל מקרה אנחנו רצים עם פייתון אז בטוח תרצו להתקין גם את סלניום עם:

$ pip install selenium

הרכיב האחרון בסיפור נקרא ChromeDriver וזו תוכנה קטנה שמחברת בין סלניום לכרום. אתם צריכים להוריד את הדרייבר ולשמור אותו על המחשב במקום נגיש, ומה שיותר מעייף זה שאתם צריכים פעם בכמה שבועות לשדרג אותו כי כרום כל הזמן רץ קדימה. את הדרייבר בכל מקרה מורידים מכאן:

http://chromedriver.chromium.org/downloads

עכשיו אפשר לעבור לתוכניות הדוגמא - ראינו כאן לפני כמה ימים איך להדפיס את כל הכותרות מאתר ynet דרך שורת הפקודה. הנה אותן הכותרות הפעם עם סלניום וכרום נטול ראש:

import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

CHROMEDRIVER_PATH = '/Users/ynonperek/bin/chromedriver'

options = Options()
options.headless = True
driver = webdriver.Chrome(CHROMEDRIVER_PATH, chrome_options=options)

# Start Here
driver.get("https://www.ynet.co.il/home/0,7340,L-184,00.html")
news = driver.find_elements_by_css_selector('a.smallheader')
for item in news:
    print(item.text)

driver.close()

החלק הראשון שמופיע לפני ה Start Here הוא הגדרת הכלים. אנחנו צריכים להגיד לסלניום איפה הוא יכול למצוא את הדרייבר שהורדנו ושאנחנו רוצים לרוץ ללא ראש. מכאן ממשיכים כמו בתוכנית סלניום רגילה רק במקום לבדוק שדברים נמצאים במקום אנחנו משתמשים בהם כדי להדפיס את המידע.

באותו האופן נוכל להדפיס את הכותרות מאתר רוטר:

import os  
from selenium import webdriver  
from selenium.webdriver.chrome.options import Options  

CHROMEDRIVER_PATH = '/Users/ynonperek/bin/chromedriver'

options = Options()
options.headless = True
driver = webdriver.Chrome(CHROMEDRIVER_PATH, chrome_options=options)

# Start Here
driver.get("http://rotter.net/")
news = driver.find_elements_by_css_selector('a[target="news"] span')
for item in news:
    print(item.text)

driver.close()

בגלל שמדובר בסלניום אנחנו יכולים לבצע פעולות באתרים ממש כאילו היינו גולשים בהם מדפדפן כרום אמיתי, כולל כל היכולות של מילוי מידע בטפסים, לחיצה על כפתורים והרצת קוד JavaScript. הבעיה היחידה עם כל הטוב הזה היא שיש לנו את דפדפן כרום שצריך לדאוג לו. הפעלת הדפדפן עם הפעלת התוכנית, ניווט לדפים אחרים וסגירתו בסוף התוכנית משפיעים על הביצועים ומייצרים קוד שרץ יותר לאט. כמה יותר לאט? על המחשב שלי הצגת הכותרות מ ynet לקחה לי 5 שניות בגירסת הסלניום לעומת חצי שניה בגירסת ה Beautiful Soup. בתוכניות יותר גדולות תוספת הזמן להמראה עשויה להיות פחות משמעותית כי אנחנו מבלים יותר זמן בשיטוטים באתר.

ההשפעה הגדולה יותר על ביצועים תגיע מהכיוון של מקביליות. נרצה לראות איך לכתוב כלי אוטומטי שלוקח מידע מאתר ואיך למקבל את העבודה שלו כדי להגיע לביצועים סבירים. אבל זה כבר חומר לפוסט הבא בסידרה.

אוהבים Web Scraping? שבוע הבא ביום חמישי בבוקר אני מתכנן להעביר וובינר בנושא שם אראה את כל הדוגמאות מכל הפוסטים האלה ועוד ויהיה גם מקום לשאלות. אם אתם פנויים מוזמנים להצטרף דרך הקישור:

https://www.tocode.co.il/workshops/64

בואו נדפיס את כל הכותרות מרוטר נט לשורת הפקודה

07/02/2019

אני לא יודע אם בטעות או בכוונה אבל כשאחד הקוראים ניסה לקחת את דוגמת הקוד שפירסמתי כאן לפני כמה ימים ולהתאים אותה לרוטר כך שתדפיס את כל הכותרות משם במקום מ ynet הסיפור לא נגמר יפה. מסתבר שה HTML של רוטר נט לא התאים למבנה ש Beatiful Soup ציפו לו.

צפיה בקוד של אתר רוטר יכולה לרמוז לנו מה הבעיה:

<!--</u>531268</u><--></u><span style='font-size:9.0pt;color:red'><b>16:29</b></span>&nbsp; <a target='news' HREF='http://rotter.net/forum/scoops1/531268.shtml'><span style='font-size:10.0pt;color:000099'></g>אחרי הספירה גדעון סער מקום רביעי</g></span></a><br>

שימו לב לתג ה g שמקיף את ההודעה על זכייתו של סער במקום הרביעי. בטעות או בכוונה לפני הטקסט מופיע שם תג סוגר. מבחינת Beautiful Soup ופרסרים רבים נוספים בשביל לסגור את ה g צריך לסגור קודם את ה a שמעליו ולכן הטקסט שאתם רואים בכלל לא נמצא בתוך אלמנט ה a.

פרסר אחר לפייתון בשם lxml יצליח להתמודד עם הבעיה אבל בצורה קצת מוזרה: במקום לקחת את הטקסט מתוך ה a אנחנו נבקש ממנו לקחת את הטקסט מההורה של ה a, וכך נצליח להגיע לכל הכותרות. התחביר ש lxml עובד איתו נקרא xpath והקוד המלא נראה כך:

import requests
from bs4 import BeautifulSoup
from lxml.html.soupparser import fromstring
from lxml import etree

url = 'http://rotter.net/'
r = requests.get(url)
r.encoding = 'iso8859-8'
root = fromstring(r.text)
headlines = [
        text for text in root.xpath('//a[@target="news"]/../text()')
        if text != '\n' and text != '\n\n' and text != '\xa0 '
]

for text in headlines:
    print(text)

בניגוד ל ynet, ברוטר ה Character Encoding לא מוגדר טוב ולכן צריך להגיד ל requests בצורה מפורשת שאנחנו רוצים עברית. פקודת ה xpath למציאת הטקסטים היתה:

root.xpath('//a[@target="news"]/../text()')

זה מאוד מזכיר CSS Selector מלבד סימן הנקודה-נקודה שמציין מעבר לאלמנט ההורה. בעברית זה יישמע משהו כמו - קח כל אלמנט a שיש לו מאפיין target עם ערך news, תמצא את ההורה של כל אחד מהם, ומההורה הזה קח את כל הטקסטים שנמצאים ישירות תחתיו. אלה בדיוק הטקסטים שיימצאו "ליד" אלמנטי ה a, בגלל שה g גרם להם לצאת מה a.

מעניין לציין שלדפדפנים אין את הבעיה הזאת ובתוך ה Developer Console של כרום אנחנו יכולים בכיף לכתוב:

$$('a[target="news"]')[0].textContent

כדי לקבל את הטקסט של האייטם הראשון.

באחד הפוסטים הבאים בסידרה אספר לכם איך להפעיל Headless Chrome מתוך פייתון כדי לקבל גישה למשהו שמתנהג הרבה יותר כמו דפדפן. ואם בא לכם ללמוד יותר על Web Scraping, לראות בשידור חי איך אני מנסה להוריד מידע מאתרים ומסתבך או להציע אתרים לקחת מהם מידע - מוזמנים להצטרף אליי בשבוע הבא ביום חמישי בבוקר לוובינר של שעה בנושא. פרטים והרשמה בחינם כאן:

https://www.tocode.co.il/workshops/64

קצת מכל דבר

06/02/2019

הדרך הכי טובה ללמוד היא קצת מכל דבר-

גם לכתוב משהו לבד בטכנולוגיה החדשה, גם לקרוא את התיעוד, גם להיתקע ולחפש תשובות ב Stack Overflow, גם ללכת למיטאפ כדי להכיר אנשים שעובדים בזה, גם להיכנס לקבוצת פייסבוק שלהם, גם להיכנס לערוץ IRC של הפרויקט, גם לראות הרצאות מכנסים ב Youtube, גם להעביר הרצאה במיטאפ ההוא שהתחלתם ללכת, גם לקרוא ספר מקצועי.

יותר נקודות מבט עוזרות לקבל תמונה עשירה יותר ועוזרות להיכנס לראש של האנשים שכותבים את הטכנולוגיה ושל אלה שכבר עובדים בה. לפני שאתם צוללים לכתיבת פרויקט שלם מאפס ב React (או כל טכנולוגיה אחרת), קחו את הזמן להכיר ולהבין את כל ה Ecosystem שלה. התוצאה תהיה פרויקט טוב יותר ובדרך כלל גם עבודה הרבה יותר מהנה.

אפשר מחר

05/02/2019

חלק מהמשימות שלכם הולכות להיות יותר קשות עם הזמן, וחלק יותר קלות.

שידרוג גירסת תוכנה שאתם תלויים בה הולך להיות יותר קשה עם כל יום שעובר. כשאתה רק גירסא אחת אחורה עוד יש סקריפטים אוטומטיים לשדרוג ומדריכים ומה לא. כשאתם שלוש גירסאות אחורה החיים הרבה יותר קשים.

אותו דבר עובד לגבי לימוד כלי עבודה. כשאתה רק מתחיל לעבוד עם git להשקיע יומיים בלהבין איך זה עובד אמור להיות די פשוט. ככל שתחכה עם זה יהיה יותר קשה למצוא את הזמן ובכלל להבין מאיפה להתחיל. כל החומר באינטרנט יראה מוכר ואתה נשאר תקוע עם ההרגשה שאתה כבר יודע הכל, למרות פערי ידע שאולי צברת.

מצד שני שינויים טכנולוגיים לרוב מחלקים דיבידנדים דווקא למי שמחכה. ריאקט ראוטר היה ממש גרוע בגירסאות הראשונות שלו, ומי שחיכה לגירסא 4 קיבל מערכת בשלה ויציבה. בצורה דומה React Native עשתה חיים ממש קלים למפתחי Mobile בהשוואה ל PhoneGap, וכמובן שהחיים ב JavaScript הרבה יותר קלים אחרי ES8.

הקושי הוא להבין איזה משימה שייכת לאיזה קטגוריה, להתאזר בסבלנות כשאנחנו רואים משימה שתהיה יותר קלה בעוד שלושה חודשים ולהתאמץ להקדים משימות שיהיו הרבה יותר קשות בעוד שלושה חודשים.

בואו נציג את כל הכותרות מ ynet דרך שורת הפקודה

04/02/2019

אתרי אינטרנט מציגים את המידע שלהם בצורה שבוני האתר מצאו לנכון, אבל לא תמיד זאת הצורה שאנחנו הגולשים הכי רוצים. המתח המוכר ביותר בין בוני אתרים לגולשים הוא בנושא הפירסומות וחוסמי הפירסומות, אבל הוא לא היחיד. אולי אתם רוצים שהמידע יהיה מאורגן אחרת, או לקבל רק חלק מהמידע, או שילוב מידע מכמה מקורות.

לשמחתכם יש די הרבה כלים קטנים שאפשר להשתמש בהם כדי לבנות סקריפטים שיעשו בדיוק את זה. בשבועיים הקרובים אני מתכנן להראות דוגמאות למצבים כאלה ולסקריפטים קטנים שעוזרים בהם - כולם לקראת וובינר Web Scraping שיתקיים ביום חמישי הבא. היום בשביל הפתיחה ניקח משימה יחסית קלה: הצגת כל הכותרות מ ynet בשורת הפקודה.

שני דברים שכדאי לשים לב אליהם עוד לפני שניגשים לכתוב את שורת הקוד הראשונה:

  1. הכי קל לקבל את הכותרות בדף ה"מבזקים" של ynet. אם תצליחו למצוא את הכפתור "מבזקים" תוכלו להעתיק את ה url משם.

  2. בדף המבזקים כל כותרת היא תגית a עם קלאס בשם smallheader.

אז מה עושים? לוקחים את ה HTML מדף המבזקים, זורקים אותו ל HTML Parser ושם שולפים את כל אלמנטי ה a עם הקלאס smallheader. בפייתון הספריה שעוזרת לקבל קובץ HTML לפי כתובת ברשת נקראת requests והספריה שמפענחת קבצי HTML נקראת Beautiful Soup. כשמחברים את שתיהן יחד מקבלים:

import requests
from bs4 import BeautifulSoup

url = 'https://www.ynet.co.il/home/0,7340,L-184,00.html'
r = requests.get(url)
soup = BeautifulSoup(r.text)
for link in soup.select('a.smallheader'):
    print(link.text)

את הסקריפט הזה אתם יכולים כבר להריץ מ cronjob ולקבל את רשימת המבזקים מתוקצרת ישר למייל שלכם על הבוקר וכך תוכלו להיות רגועים ולדעת שהעולם לא התמוטט כשישנתם.

טיפול ב Exceptions ב Python

03/02/2019

לא כל דבר שמנסים מצליח, גם בחיים וגם בתכנות. בפרק זה נלמד על חריגים בפייתון ונראה מה קורה כשמשהו משתבש וכיצד ניתן להחליט בעצמנו על תוכנית מילוט.

המשך קריאה

שתי טעויות של בתי ספר

02/02/2019

הטעות הראשונה היא פיקוח יתר, וזה מה שקורה במהלך השיעור. המורה מדברת והילדים יושבים למלא קווים בחוברות עבודה שאי אפשר ללכת לאיבוד בהן. אגב לתרגולים בקודסקול יש את אותה בעיה. אנחנו לומדים באמת דרך משימות פתוחות שדורשות בניה של משהו מקצה לקצה.

הטעות השניה היא היעדר פיקוח, וזה מה שקורה בהפסקה. כאילו בשביל להתמודד עם פיקוח היתר וכדי שהילדים לא "יתפוצצו" אנחנו נותנים להם להוציא קיטור ולרוץ בלי השגחה רבע שעה בין שיעור לשיעור. בפועל ילדים לא יכולים לעשות שום דבר מועיל בהפסקה בדיוק בגלל שאין הכוונה.

בית ספר אידאלי נותן לילדים יום שלם ללכת באמצע- לבנות את הדברים הנכונים עם ההכוונה הנכונה, בלי לתת הוראות מדויקות ובלי לפחד שהילדים יטעו או יבזבזו את הזמן. זה בסדר גמור שצוות ינסה להרים מופע ובסוף לא יצליח, אבל עם הכוונה נכונה מסביב לשעון הם ילמדו מהתהליך.

(*) "אי אפשר ללכת לאיבוד בהן" - אם שואלים אתכם כמה זה 2 ועוד 2 ותכתבו 5 הצלחתם לכתוב את התשובה הלא נכונה. זה לא אומר שהצלחתם ללכת לאיבוד. ללכת לאיבוד זה לבנות משהו אחר לגמרי ממה שכותב השאלה התכוון ועדיין להרגיש טוב וללמוד מהתהליך. ללכת לאיבוד זה שקורה כשאני נותן משימה מסוג "תמצאו כמה שיותר בעיות אבטחה בקוד המערכת ההיא", או "בנו בוט לטלגרם שעושה משהו נחמד". גם אם לא מצאתם אף בעיית אבטחה או שהבוט לטלגרם שלכם לא מצליח לנסח תשובה נורמלית לשום שאלה, עדיין למדתם המון.

השנה 2019, ואני עדיין שולח הודעות SOAP

01/02/2019

כמה לקחים מעבודת אינטגרציה קטנה שאני עובד עליה, אבל קודם בשתי מילים מה זה SOAP ולמה אנחנו מקטרים.

אז בתחילת שנות ה 2,000 היה פורמט של קבצים שנקרא XML וכולם עפו עליו וחשבו שזה הדבר הכי טוב מאז הלחם הפרוס או משהו. בכל מקרה אחת החברות שעפה על XML נקראת מייקרוסופט והם בדיוק חיפשו דרך לבנות ארכיטקטורה גנרית לתקשורת בין מערכות מבוזרות. הם חשבו על המון דברים והתוצאה היתה שבשביל לקבל למשל פרטי לקוח דרך API היית שולח בקשה שנראית בערך כך:

<?xml version="1.0"?>
<soap:Envelope 
          xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <soap:Body>
    <m:GetCustomer 
          xmlns:m="http://www.example.org/customers">
      <m:CustomerId>43456</m:CustomerId>
    </m:GetCustomer>
  </soap:Body>
</soap:Envelope>

ומקבל בחזרה תשובה שנראית בערך כך:

<?xml version='1.0' ?>
<env:Envelope 
        xmlns:env="http://www.w3.org/2003/05/soap-envelope" >
 <env:Body>
    <m:GetCustomerResponse 
          xmlns:m="http://www.example.org/customers">
       <m:Customer>Foobar Quux, inc</m:Customer>
    </m:GetCustomerResponse>
 </env:Body>
</env:Envelope>

כל העסק הזה היה עטוף בענן סוכר של כלים בתוך ה Visual Studio כדי שמתכנתים אף פעם לא יצטרכו לראות את ה XML-ים האלה והכל נוצר בשבילכם באופן אוטומטי בלחיצת כפתור.

עד שיום אחד מתכנתים הפסיקו לעבוד ב Visual Studio והלכו לכתוב מערכות אינטרנט ב Rails (כן ריילס היה פופולרי ממש ב 2005) ומפה לשם שמנו לב שהרבה יותר קל פשוט לשלוח הודעה בפרוטוקול REST:

GET /customers/43456 HTTP/1.1
Host: www.example.org

ולקבל את התשובה ב JSON:

{'Customer': 'Foobar Quux, inc'}

אתם יכולים לקרוא עוד על הכישלון של SOAP במאמר המצוין בקישור הזה (ממנו גם לקחתי את הדוגמאות):

http://keithba.net/simplicity-and-utility-or-why-soap-lost

עכשיו בחזרה אלינו וללקחים מ SOAP-

  1. טכנולוגיות לא מתות. זאת הסיבה שאנגולר1 תמשיך להיות דבר עוד המון שנים.

  2. מוצרים טכנולוגיים מספיק וותיקים יכללו טכנולוגיות "מתות", כי ברגע שיש בסיס קוד גדול קשה מאוד להחליף את הכל.

  3. למודול soap ב npm יש באזור השמונים אלף הורדות בשבוע. זה אולי לא מתקרב ל 7 מיליון הורדות של אקספרס אבל עדיין מזכיר שאני לא היחיד שהיה צריך להתחבר לשרת SOAP השבוע.

שלוש דרכים להתקבל לעבודה הבאה

31/01/2019

בכל הנוגע לראיונות עבודה אפשר לשפר משמעותית את הסיכויים שלכם להתקבל אם תהיו ממוקדים בתהליך ההכנה שלכם ותכוונו להיות הכי טובים. אבל כמו שיש מגוון של טכנולוגיות יש גם מגוון של טיפוסים שמגייסים מעוניינים בהם. אלה ה-3 המרכזיים שאני מכיר:

המשך קריאה

הפונקציה re.compile ב Python

30/01/2019

בכל פעם שאתם משתמשים בביטויים רגולאריים בתוכנית שלכם המחשב לוקח את "קוד" הביטוי עצמו (שהוא טקסט), הופך אותו לאוביקט בזיכרון שאיתו הוא יכול לבדוק מחרוזות (תהליך שנקרא compilation) ואז משתמש באוביקט הזה בזיכרון כדי לבדוק טקסטים. פייתון מאפשרת לבצע את שני השלבים האלה ״במכה אחת״ או בנפרד.

הפקודה הבאה מבצעת קומפילציה של ביטוי רגולרי וגם בודקת אם מחרוזת מתאימה לביטוי:

import re
re.search('l+', 'hello')

דרך אחרת היא לשבור את הפעולה ל-2 כמו שממחישה התוכנית הבאה, שעושה בדיוק את אותו דבר רק נותנת לך ״נקודת התערבות״ אחרי הקומפילציה של הביטוי:

import re
scanner = re.compile('l+')
scanner.search('hello')

הפונקציה re.compile מחזירה את אוביקט הביטוי הרגולרי, מה שקראתי כאן scanner איתו אפשר לבדוק מחרוזות. שמירת אוביקט זה במשתנה נפרד נותנת שני יתרונות:

  1. שיפור ביצועים כשאתם מחפשים את אותו ביטוי רגולרי בכמה מחרוזות (כי מבצעים את הקומפילציה רק פעם אחת).

  2. סדר בקוד ואפשרות להפריד בין ״קוד הביטוי הרגולרי״ לבין הקוד שמשתמש בו כדי לזהות תבניות במחרוזות.

קומפילציה של ביטוי רגולארי זה רעיון שאינו ייחודי בפייתון ואנחנו מוצאים אותו בשפות רבות אחרות. כך לדוגמא ב perl היינו מקמפלים ביטוי רגולארי ושומרים אותו במשתנה:

use strict;
use v5.24;

my $scanner = qr { l+ }x;

if ("hello" =~ $scanner) {
  say "Match!";
}

לרובי יש פונקציה בשם compile אבל היא בסך הכל שם נרדף לפונקציה Regexp.new ולכן כך יראה אותו הקטע ברובי:

scanner = Regexp.new('l+')

if scanner.match('hello')
  puts "Match!"
end

ו JavaScript השאירו רק את פונקציית הבנאי ושם הקוד יראה כך:

const scanner = new RegExp(/l+/)
if ('hello'.match(scanner)) {
    console.log('Match!');
}