תרגילי פונקציות ופרמטרים בפייתון
לומדים פייתון ורוצים לתרגל את כל האפשרויות להעביר ולהחזיר ערכים מפונקציות? הנה כמה תרגילים שאני מקווה שיעזרו.
טיפים קצרים וחדשות למתכנתים
לומדים פייתון ורוצים לתרגל את כל האפשרויות להעביר ולהחזיר ערכים מפונקציות? הנה כמה תרגילים שאני מקווה שיעזרו.
רוב בעיות הביצועים ביישומי ריאקט הן תוצאה של עודף קריאות לפונקציית render. פונקציה זו אמורה להיקרא אוטומטית כל פעם שמשהו ב props או ב state משתנה כדי לחשב את המצב החדש של הקומפוננטה. אבל, אם ריאקט חושב בטעות שמשהו השתנה למרות ששום דבר לא השתנה ב state וב props, הוא עלול לעשות הרבה חישובים מיותרים ולפגוע בביצועים של היישום.
לכן כשיש לנו בעיית ביצועים ביישום ריאקט השאלה הראשונה שנרצה לברר היא איזה פעולות render קורות ברגעים הלא נכונים, והאם אפשר לבטל חלק מהקריאות. ברגע שתמצאו ותבטלו את הקריאות המיותרות ל render היישום שלכם יתחיל לרוץ הרבה יותר מהר.
מה שיפה בריאקט זה שאין יותר מדי קסמים - ההחלטה אם לקרוא או לא ל render היא תוצאה של פונקציה אחת בלבד: הפונקציה shouldComponentUpdate. זה אומר שאם אתם חושדים בקריאות מיותרות ל render קודם כל תרצו להיכנס לפונקציה הזאת ולראות מה היא מחזירה ואם עדיף שהיא תחזיר משהו אחר. בגדול אם פונקציה זו מחזירה true אז render ייקרא, ואם false אז נדלג על render.
מימוש ברירת המחדל של shouldComponentUpdate מחזיר תמיד true, מה שאומר שכל שינוי ב props או ב state של הפקד יביא להפעלת render מחדש. מחלקת אב בשם React.PureComponent מספקת מימוש קצת יותר יעיל שמבצע השוואת Shallow Comparison בין ה props החדש לישן ובין ה state החדש לישן, ורק אם יש הבדל יחזיר true.
לכן בשביל לזהות ולתקן פעולות render מיותרות נרצה לבצע:
לוודא ש render מופעל יותר פעמים ממה שהיינו רוצים (אפשר לשים נקודת עצירה או אפילו הדפסת console.log)
לממש בעצמנו את shouldComponentUpdate ובעזרת נקודת עצירה להבין בדיוק מה אנחנו רוצים להשוות ולהגיע לתנאים הנכונים
אם גילינו שהמימוש שלנו מבצע Shallow Compare - אפשר למחוק את הפונקציה שכתבנו ולהחליף את היררכיית הירושה כך שהפקד יירש מ PureComponent (לא חייבים).
בואו נראה את זה בדוגמא קצרה. ניקח את הקוד כאן שמציג עשרה ריבועים, אחד אדום והשאר אפורים:
אם אתם רואים את זה מהמייל ולא בא לכם להיכנס לקודפן זה קוד ה JavaScript שמפעיל את הסיפור:
class Item extends React.PureComponent {
render() {
const cls = this.props.winner ? 'red box' : 'grey box';
return <div className={cls} onClick={this.props.onClick} />
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { winner: 4 };
}
shuffle() {
this.setState({
winner: _.random(10),
})
}
render() {
return (
<div>
{_.range(10).map(i => (
<Item winner={this.state.winner == i} onClick={() => this.shuffle()} />
))}
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('main'));
קודם כל נרצה לשאול - בעת לחיצה על אחד הכפתורים (שגורמת להזזה של הריבוע האדום); כמה render-ים מבוצעים? דרך קלה לענות על זה היא להוסיף פקודת console.count בתוך פונקציית ה render של Item. כלומר הקומפוננטה תיראה כך:
class Item extends React.PureComponent {
render() {
console.count('Item::render');
const cls = this.props.winner ? 'red box' : 'grey box';
return <div className={cls} onClick={this.props.onClick} />
}
}
התוצאה הלא מפתיעה היא שאחרי כל לחיצה יש לנו 10 הודעות:
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 1
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 2
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 3
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 4
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 5
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 6
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 7
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 8
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 9
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 10
זה בטח לא דבר טוב - היינו מעדיפים שהפונקציה render תיקרא רק פעמיים: אחת עבור הריבוע שהופך מאפור לאדום והשניה עבור הריבוע שהופך מאדום לאפור. מאחר והריבועים יורשים כולם מ PureComponent זה אומר שאחד השדות ב props או ב state יצא שונה בכל אחד מהריבועים. בשביל לגלות איזה שדה זה נוסיף מימוש משלנו עם הודעות הדפסה מתאימות:
class Item extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const sameWinner = nextProps.winner === this.props.winner;
const sameOnClick = nextProps.onClick === this.props.onClick;
console.log('<<<<<- ');
console.log('Same class: ', sameWinner);
console.log('Same on click: ', sameOnClick);
console.log('>>>>>- ');
return !(sameWinner && sameOnClick);
}
render() {
console.count('Item::render');
const cls = this.props.winner ? 'red box' : 'grey box';
return <div className={cls} onClick={this.props.onClick} />
}
}
והתוצאה:
<<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 1
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 2
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 3
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 4
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 5
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 6
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 7
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 8
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 9
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 <<<<<-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same class: true
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Same on click: false
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 >>>>>-
console_runner-1df7d3399bdc1f40995a35209755dcfd8c7547da127f6469fd81e5fba982f6af.js:1 Item::render: 10
ואכן אנחנו רואים בהדפסה שרק הריבועים שצריכים להשתנות קיבלו ערכים אחרים כשאנחנו משווים את props.winner, אבל בהשוואת props.onClick כל ריבוע קיבל ערך אחר בכל render. הסיבה נעוצה בשורה:
<Item winner={this.state.winner == i} onClick={() => this.shuffle()} />
בכל פעם שאנחנו מפעילים את ה render של App אנחנו גם יוצרים פונקציית onClick חדשה! ולכן כל render של Item מקבל בתור Property את ה onClick החדשה, ההשוואה מחזירה שיש הבדל ב Property זה וכך מגיעים לקריאות מיותרות ל render. דרך קלה לתקן את זה אחרי שהבנו את הבעיה היא להעביר את ה bind לבנאי כך שפקד App יראה כך:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { winner: 4 };
this.shuffle = this.shuffle.bind(this);
}
shuffle() {
this.setState({
winner: _.random(10),
})
}
render() {
return (
<div>
{_.range(10).map(i => (
<Item winner={this.state.winner == i} onClick={this.shuffle} />
))}
</div>
);
}
}
ואכן אחרי השינוי כל לחיצה מייצרת רק שתי הדפסות Item::render במקום 10. הקוד המתוקן המלא נמצא בקודפן הזה:
לכן הפונקציה shouldComponentUpdate היא המפתח להבין ולפתור בעיות ביצועים בריאקט. אל תפחדו להשתמש בה ותרגישו חופשי להיות יצירתיים. זה בסדר גמור לכתוב מימושים משלכם שיהיו יותר יעילים מהמימוש הדיפולטי של PureComponent עבור האפליקציות שלכם. בהצלחה בחיפושים.
אפשר לבשל בלי אף תבלין אבל התוצאה תהיה משעממת ולא בטוח שתהנו מהארוחה. תבלינים לא מוגבלים רק לאוכל ובעצם כל דבר שאנחנו עושים יכול להיות נחמד יותר אם נוסיף לו כפית סוכר - גם לימודי תכנות.
אלה חלק מה"תבלינים" שאני משתמש בהם כשאני מנסה ללמוד טכנולוגיה חדשה:
לענות לאנשים על שאלות בכל מיני קבוצות פייסבוק של הטכנולוגיה הזאת
ללכת למיטאפ או לשמוע הרצאה ביוטיוב
לקרוא שטויות ברדיט בפורום של הטכנולוגיה הזאת
לנסות למצוא פרויקטי קוד פתוח בטכנולוגיה הזאת ולראות מה הם עשו
לכתוב משהו קטן שפותר לי בעיה אמיתית או עוזר לי עם משהו אמיתי בחיים בעזרת הטכנולוגיה הזאת
כן ברור שכדאי שיהיה לכם תהליך מסודר ותוכנית לימודים ושיעורים ותרגולים ופרויקט מלווה וכל הדברים האלה, אבל אם אתם יכולים להוסיף לזה את התבלין הנכון הכל הולך להיות הרבה יותר קל. (וגם ההיפך- אם אתם נתקעים ומרגישים שלא מתקדמים בלימוד, אולי צריך להחליף את התבלינים).
איזה שפה כדאי לבחור? ואיזה טכנולוגיה כדאי ללמוד? השאלות האלה מטרידות לא מעט אנשים שפוחדים לבחור את הדבר הלא נכון וללמוד משהו שלא יוכלו להשתמש בו. זה קצת מזכיר מישהו שמתלבט איזה שפה ללמוד כי הוא מתכנן רילוקיישן ולא בטוח לאיזה ארץ ללכת - הנה הפחד: להשקיע שנים בלימוד יפנית ובסוף להיתקע כי אין עבודה ביפן.
מה שקורה בפועל מאוד שונה (בתכנות בטוח, בשפות כנראה שלא). אחרי שלומדים טכנולוגיה אחת מגלים שאותם העקרונות שהשקעת 80% מתהליך הלמידה בלהבין אותם, עקרונות אלה ממש רלוונטיים לעוד 50 שפות תכנות וטכנולוגיות מקבילות. כלומר אחרי שלמדתם פייתון אם תרצו ללמוד רובי תצטרכו "להשלים" רק 20% ממה שלמדתם.
אבל רגע חכם גדול, אם זה כך אז למה מודעות הדרושים מספרות סיפור כל כך שונה? הנה דרישות לדוגמא שמצאתי באולג'ובס:
-7 שנות ניסיון בפיתוח Frontend. Angular הכרחי. המשרה מיועדת לנשים וגברים כאחד.
אם באמת המעבר מריאקט לאנגולר כל כך פשוט, למה הם כותבים כאן Angular הכרחי? ואם יגיע אליהם מתכנת ריאקט עם 7 שנים ניסיון בפיתוח Frontend לא יקחו אותו? הסיבה היא פשוטה ונקראת עודף פניות. בסקר (יחסית ישן, אבל מאמין שהתוצאות היום דומות) שמצאתי נשלחו 122 קורות חיים בממוצע לכל משרה בהייטק. לראש צוות שצריך לעבור על קורות החיים האלה המספרים האלה פשוט גדולים מדי, אז מוסיפים דרישות כדי שיהיה אפשר בכלל לעבור על זה.
מסקנות? תלמדו מה שקל לכם ללמוד ותתחילו לכתוב בזה קוד כמה שיותר מהר. ככל שתכתבו יותר קוד כך תדעו יותר טוב מה מעניין אתכם ויהיה לכם קל לנווט למקומות האלה וגם למצוא בהם עבודה. ממילא אם אתם מכוונים לבנות תיק עבודות ולהתקבל איתו תצטרכו לעקוף את חברות ההשמה כי אין לא יהיה להם מושג איפה לסווג אתכם.
מנגנון של תכנות מונחה עצמים שנקרא ירושה מאפשר לשתף ממשק בין מספר מחלקות ולא רק את הקוד עצמו. בואו נראה בקצרה מהו ממשק ולמה צריך לשתף אותו.
אחת הבעיות עם אבטחת מידע היא שבעיות אבטחה זה משהו שלא כל אחד רואה במבט ראשון. אם כפתור באתר שלכם לא עובד מיד יהיו משתמשים שיכעסו שהכפתור לא עובד, אבל אם הקאפצ'ה לא מחובר אני חושב שמעט מאוד אנשים הולכים לצעוק על זה (וגם אם הם יצעקו בדרך כלל לא יהיה מי שיקשיב).
אז איך קאפצ'ה בכלל עובדת? ואיך היא יכולה להישבר? טוב ששאלתם.
כשאתם פונים לדף טופס שיש בו קאפצ'ה השרת פונה לספק הקאפצ'ות שלו ומבקש קאפצ'ה חדשה. הקאפצ'ה היא בסך הכל תמונה שיש לה את החלק הויזאלי (מה שאתם רואים) ומזהה קאפצ'ה. אחרי שאתם כותבים את האותיות המוזרות שבתמונה ומגישים את הטופס השרת שולח את התשובה שלכם יחד עם מזהה הקאפצ'ה לשרת הקאפצ'ות שלו כדי לבדוק שאתם בני אדם ולא רובוטים. אם התשובה שלכם נכונה הוכחתם שאתם בני אדם והשרת יגיש לכם את העמוד המתאים.
רואים כבר איפה זה יכול להישבר?
הבעיה המרכזית בהטמעת מנגנון כזה היא מה קורה כשהשרת שלכם מנסה לבדוק מול ספק הקאפצ'ה שלו האם קאפצ'ה היא וולידית בפעם השניה. או במילים אחרות מי אחראי על "למחוק" את הקאפצ'ה אחרי ששלחתם אותה וקיבלתם את המידע פעם אחת. מצד אחד אולי אין לנו יותר מדי תמונות במאגר ונשמח לשלוח את אותה קאפצ'ה לכמה משתמשים (או אפילו לאותו משתמש כמה פעמים), אבל מצד שני משתמש שמכיר את הצמד "מזהה קאפצ'ה <-> האותיות" יכול להשתמש בזה כדי לשלוח בקשות אוטומטיות לשרת.
הנה התרחיש הבעייתי:
משתמש פותר את הקאפצ'ה ורושם אצלו את כל הפרטים בטופס שנשלחים לשרת, כולל את מזהה הקאפצ'ה והפיתרון שלה.
משתמש לוקח את הבקשה ההיא שעברה ומשנה בה חלק מהמידע. למשל אם זה טופס שמחזיר מידע על אנשים לפי ת.ז. אפשר לשנות רק את תעודת הזהות. את מזהה הקאפצ'ה והפיתרון שלה משאירים כמו שהיו.
השרת שולח לספק הקאפצ'ה שלו את מזהה הקאפצ'ה והפיתרון ומקבל תשובה שהכל תקין וזה אכן פיתרון טוב לקאפצ'ה.
והנה קיבלנו מידע חדש בלי לפתור את הקאפצ'ה פעם שניה.
אז כן כשאתם מטמיעים פיתרון אבטחת מידע חשוב לוודא שאתם מבינים ממה הפיתרון הזה אמור להגן, איך הוא אמור להגן מהאיום המסוים ולבדוק ספציפית שהמנגנון אכן מגן מהאיום שדיברנו עליו. אחרת סתם שיגעתם את כולם עם האותיות המקושקשות שלכם.
מה יותר קשה - למצוא את העבודה הראשונה בהייטק או להתקדם לעבודה השניה? למצוא את הלקוח הראשון בתור עצמאי או להתקדם ללקוח החמישי? לבנות את המשחק הראשון ב App Store או הרביעי?
בהסתכלות אחורה על כל משימה שהצלחתם אני חושב שקשה למצוא דוגמאות לכאלה שבהן הצעד הראשון היה הכי קשה אוביקטיבית. יש הרבה יותר משרות Juniors בהייטק מאשר משרות Senior ודרישות המשרה הן הרבה יותר כלליות. לכן אוביקטיבית הכי קל להיכנס למשרת סטודנט תוך כדי הלימודים, וככל שאתם מתקדמים בתפקיד יהיה לכם יותר קשה להמשיך ולהתקדם.
אותו דבר לגבי הורדות של האפליקציה שכתבתם: החברים ובני המשפחה שלכם יורידו את האפליקציה בלי להבין בכלל מה היא עושה, רק בגלל שהם שמחים לעזור לכם. אפילו אם אין לכם חברים או משפחה תופתעו לגלות שיש אנשים שמורידים כל אפליקציה חדשה שיוצאת רק בשביל הקטע, כי הם רוצים להרגיש בעניינים.
והלקוח הראשון? הוא כנראה חבר-של-חבר שרק מחכה שתהפכו לעצמאים כדי שיוכל לקנות מכם.
המעבר מאפס לאחד הוא החלק שאובייקטיבית הכי קל בכל תהליך יצירה, ועם זאת הוא מצליח להיות החלק הכי מפחיד בתהליך. אז אנחנו מחכים, ומדברים, ולוקחים עוד קורס, ומבזבזים עוד שנה.
יותר טוב לקחת אוויר, להיזכר בכל הצעדים הראשונים שכן הצלחתם לעשות במשימות אחרות ולהתקדם. מקסימום תגיעו ל-1, תתחרטו ותוכלו להרגיש טוב יותר שהחלום הזה כנראה לא בשבילכם.
שימוש אחד של Web Scraping הוא לביצוע פעולות או שליפת מידע בצורה אוטומטית. לדוגמא אנחנו יכולים לכתוב סקריפט שבאופן אוטומטי יבדוק מחיר של מוצר באמזון כל בוקר ואם המחיר נמוך במיוחד ישלח לנו אימייל.
שימוש אחר של Web Scraping, עליו ארצה לדבר היום, הוא היכולת להסתכל במכה אחת על המון מידע ברשת ולהגיע למסקנות שבצורה ידנית היה קשה מאוד להגיע אליהן. בתחום של Machine Learning אתם תפגשו אנשים שלוקחים את המידע מהרשת ונותנים למחשב להבין מתוך מידע זה מה הולך לקרות מחר. וגם בלי Machine Learning אפשר לעשות דברים מעניינים עם מידע כשמסתכלים עליו אחרת ממה שמי שכתב אותו התכוון.
כך בשביל למצוא את המילים שנוני מוזס הכי אוהב אפשר להסתכל בכל המבזקים ב ynet ולספור כמה פעמים מופיעה כל מילה במבזקים.
כשאנחנו רוצים לקחת את כל המילים באתר בכל הדפים שלו עדיף לשלוח מספר בקשות במקביל (באזור ה 8-10 במקביל נותן תוצאה טובה). 8 בקשות במקביל עובר מתחת לרדאר של רוב האתרים, בוודאי הגדולים. לפני כמה ימים פרסמתי כאן דוגמת קוד שמביאה את כל המבזקים מ ynet:
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)
עכשיו אנחנו רוצים להמשיך את התוכנית הזאת צעד נוסף - עבור כל מבזק נרצה לקחת הכתבה עצמה ולספור את המילים שבה. בשביל לפתוח מספר בקשות רשת במקביל אני משתמש בספריה שנקראת requests_futures
. הספריה מאוד פשוטה ועובדת בממשק שנראה כמו requests, רק שהיא שולחת את הבקשות במקביל ומאפשרת לכם להוסיף קוד טיפול בכל תשובה.
בהנחה שיש לכם רשימה של URL-ים תוכלו להשתמש בקוד שנראה בערך כך כדי להביא את כולם מהרשת במקביל ולראות את סטטוס התשובה של כל אחד:
q = [session.get(url) for url in urls]
results = [f.result() for f in q]
ובתוך תוכנית מלאה הסיפור נראה כך:
import requests
from bs4 import BeautifulSoup
from requests_futures.sessions import FuturesSession
import collections
url = 'https://www.ynet.co.il/home/0,7340,L-184,00.html'
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html5lib')
urls = [f'https://www.ynet.co.il{item.get("href")}' for item in soup.select('a.smallheader')]
word_counter = collections.Counter()
def parse_ynet_article(resp, *args, **kwargs):
page = BeautifulSoup(resp.text, 'html5lib')
text = page.select('.art_body span')[0].text
word_counter.update(text.split())
session = FuturesSession()
session.hooks['response'] = parse_ynet_article
q = [session.get(url) for url in urls]
results = [f.result() for f in q]
print(word_counter)
הפונקציה parse_ynet_article
תקרא באופן אוטומטי בכל פעם שהסקריפט סיים להוריד כתבה מ ynet. היא מפענחת את ה HTML, סופרת את המילים בתוכן הכתבה ומוסיפה אותן ל Counter. כל זה קורה מ-8 תהליכונים במקביל ומנוהל באופן אוטומטי על ידי requests_futures
. בצורה דומה יכולתם לשמור את כל ה HTML-ים של האתר אליכם או לעשות כל עיבוד אחר על התוכן.
יש הרבה מאוד גישות ודעות שונות איך להחליט איזה פרויקטים לקחת בתור פרילאנסרים ואיך יודעים איזה פרויקט יקדם אתכם בצורה הטובה ביותר. הגישה שעבדה בשבילי היא שילוב של שתי שאלות:
עד כמה הפרויקט הזה יכול לעזור ללקוח?
עד כמה הפרויקט הזה מוציא אותי מפוקוס ופוגע בפרויקטים אחרים שחשובים לי?
לרוב הפרילאנסרים בתחילת הדרך אין עדיין פוקוס ולכן נרצה למצוא פרויקטים שתהיה להם כמה שיותר השפעה על הלקוח והעולם שלו. ככל שההשפעה גדולה יותר כך גדל הסיכוי שתסיימו את הפרויקט עם לקוח שירוץ לספר לכל החברים שלו. בגלל זה לא הייתי ממליץ לפרילאנסר גם בתחילת הדרך לבנות דפי נחיתה למשרדי פירסום או לפתוח פרויקט ב XPlace. אלה מקומות שאולי קל למצוא עבודה אבל קשה להפתיע לטובה. העבודות הכי טובות שהיו לי בתור פרילאנסר מתחיל היו דווקא פרויקטים בהתנדבות או כמעט בהתנדבות.
אחרי כמה פרויקטים טובים הלקוחות שלכם כבר יתחילו לספר לחברים שלהם ואז אתם מתחילים להתמודד עם בעיה חדשה שנקראת פוקוס. ככל שהפרויקטים שלכם יותר קרובים אחד לשני כך יהיה לכם יותר קל לעשות עבודה טובה יותר - כי אפשר לשתף קוד ורעיונות בין הפרויקטים. רופא עיניים מומחה שעושה את אותה פרוצדורה עשר פעמים ביום במשך שנים ממשיך לשפר את הטכניקה עם כל יום שעובר וכך מצליח לתת ערך גדול יותר ללקוחות שלו.
כל השאלות האחרות כגון האם משלמים לי מספיק? האם הלקוח נחמד? האם אני אלמד משהו חדש מהפרויקט? איך זה יראה בתיק עבודות שלי? באיזה שפת תכנות הפרויקט? ועוד שאלות רבות נוספות הן לרוב לא יותר מהסחות דעת. תתמקדו בהשפעה של הפרויקט על הלקוח שלכם וכל השאר יסתדר מעצמו.
התחביב שלי הוא תכנות, זה ברור. אני אוהב לכתוב קוד, לקרוא קוד, לדבר עם אנשים על קוד, לקרוא פוסטים על פריימוורקים חדשים. בקיצור אני מאלה.
ובגלל זה אחד הרגעים השמחים שהיו לי אחרי שהתחלתי ללמוד שפה חדשה היה כשהצלחתי לקרוא פוסטים מקצועיים בצרפתית. התחלתי ברדיט כמובן ומשם הגעתי לבחור הזה וממנו לעוד הרבה חברים שלו. ומכאן המחגר התחיל להסתובב: ככל שהצלחתי לקרוא ולהבין יותר פוסטים כך חיפשתי עוד ועוד מהם, מה שאומר שקראתי יותר וכך יכולת הקריאה שלי השתפרה.
והנה עוד אחד- לפני כמה חודשים העליתי לכאן קורס וידאו בנושא git. כמה אנשים ראו את הקורס וחשבו שהוא מספיק מעניין כדי להזמין אותי להרצות על גיט אצלם בחברה. מההרצאה והשאלות של המשתתפים גם אני למדתי יותר. על חלק מהלקחים האלה כתבתי כאן בבלוג וזה עודד עוד כמה אנשים להזמין אותי להרצות גם אצלם על גיט ומפה לשם אני יודע היום גיט לא רע בכלל.
רוצים לבנות לעצמכם תוכנית לימודים (לא משנה למה)? תתחילו עם המחגר. כשהגלגל זז רק קדימה ההצלחה מובטחת.