<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: CopyPasteEngineer</title>
    <description>The latest articles on DEV Community by CopyPasteEngineer (@copypasteengineer).</description>
    <link>https://dev.to/copypasteengineer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F343174%2F8273ec4d-27ad-4d9c-89e8-223a98c76a4e.png</url>
      <title>DEV Community: CopyPasteEngineer</title>
      <link>https://dev.to/copypasteengineer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/copypasteengineer"/>
    <language>en</language>
    <item>
      <title>Python Genetic Algorithm - เลือกทำเลร้านขายลูกชิ้นในม็อบด้วย AI 😂</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sun, 25 Oct 2020 12:09:48 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-genetic-algorithm-ai-29nj</link>
      <guid>https://dev.to/copypasteengineer/python-genetic-algorithm-ai-29nj</guid>
      <description>&lt;p&gt;ขอเริ่มจากเล่านิทานก่อนนะครับ 555 &lt;em&gt;สมมุติว่าเราอยู่ในองค์กรสายลับระดับชาติในชื่อ &lt;strong&gt;"ภาคีลูกชิ้นทอด"&lt;/strong&gt; ที่กำลังวางแผนแทรกซึมเข้าไปในม็อบเพื่อขายลูกชิ้นและไก่ทอดในม็อบให้ได้มากที่สุด แน่นอนว่าเมื่อเราส่งสายลับมืออาชีพของเราลงไปแล้ว ไม่ว่าใครก็ต้องอดใจไม่ไหว ด้วยลูกชิ้นทอดและน้ำจิ้มสูตรเด็ด ที่ส่งกลิ่นหอมแพร่กระจายออกไปเป็นกิโล &lt;strong&gt;ทำให้ผู้ร่วมชุมนุมที่อยู่ในรัศมีทุกคนจะตามกลิ่นมาซื้ออย่างแน่นอน!&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;เนื่องจากองค์กรได้รับข้อมูลจากสายสืบถึงพื้นที่ที่มีความเป็นไปได้ที่จะจัดตั้งม็อบก่อนใครเสมอ จึงสามารถคาดเดาตำแหน่งของคนที่เข้าร่วมชุมนุมล่วงหน้าได้อย่างแม่นยำ โดยเราได้รับมอบหมายให้หาจุดที่จะส่งสายลับลูกชิ้นทอดทั้ง 10 คน &lt;strong&gt;เข้าไปประจำการล่วงหน้าตามจุดต่าง ๆ 10 จุด ที่จะสามารถทำยอดขายรวมได้ที่สุด&lt;/strong&gt; ชะตากรรมของทั้งประเทศอยู่ในมือของเราแล้ว!!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;โชคดีที่เรารู้จักเทคนิคที่เรียกว่า &lt;strong&gt;Genetic Algorithm&lt;/strong&gt; จึงสามารถนำมาใช้หาตำแหน่งที่ดีได้เสมอ ทำให้องค์กรสามารถทำภารกิจแทรกซึมได้สำเร็จทุกครั้งไป...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;...ในบทความนี้ มาดูกันว่า Genetic Algorithm คืออะไร แล้วมันจะช่วยองค์กรได้อย่างไรนะครับ??&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kDcSdTd3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/chjb3jbk54q6hd48xxaf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kDcSdTd3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/chjb3jbk54q6hd48xxaf.jpeg" alt="cover.jpeg"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;จาก &lt;a href="https://mgronline.com/live/detail/9630000108268"&gt;https://mgronline.com/live/detail/9630000108268&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: สำหรับท่านที่งง ๆ ว่าลูกชิ้นทอดเกี่ยวอะไร ลองอ่านเนื้อเรื่องเพิ่มเติมได้ครับ 5555: &lt;a href="https://positioningmag.com/1302924"&gt;เปิดใจ CIA หน่วยเคลื่อนที่เร็วประจำม็อบ “ลูกชิ้นทอด-ไก่ทอด” ยอดขายพุ่งเฉียดวันละหมื่น!&lt;br&gt;
&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Genetic Algorithm
&lt;/h1&gt;

&lt;p&gt;พักเรื่องนิทานเอาไว้สักครู่ มารู้จักกับ Genetic Algorithm กันก่อน Genetic Algorithm (GA) เนี่ยก็เป็น algorithm อย่างหนึ่งที่เอาไว้ใช้หาคำตอบ (solution) ที่ให้ค่าของผลลัพธ์มากที่สุด จริง ๆ มันก็คือ algorithm สำหรับการทำ optimization อย่างหนึ่งนั่นเองครับ&lt;/p&gt;
&lt;h1&gt;
  
  
  ตัวอย่าง Use Case
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;การหาเส้นทางนำของคลังสินค้าไปส่งตามจุดต่าง ๆ โดยใช้ต้นทุนน้อยที่สุด&lt;/strong&gt; &lt;br&gt;
เราก็จะนิยามโจทย์ให้ Genetic Algorithm (GA) ว่าให้หา &lt;strong&gt;คำตอบ (solution)&lt;/strong&gt; ซึ่งก็คือลำดับการเดินทาง เช่น [คลังสินค้า, ร้าน1, ร้าน7, ร้าน3, คลังสินค้า, ร้าน5, ...] ออกมา โดยต้องเป็นคำตอบที่ใช้ &lt;strong&gt;ต้นทุน (cost)&lt;/strong&gt; หรือ objective value น้อยที่สุด ซึ่งอาจจะเป็นค่าน้ำมันหรือเวลา ก็ตามแต่ที่เราจะกำหนด จากนั้น GA ก็จะไปทำการลองผิดลองถูกเพื่อหาคำตอบที่ดีที่สุดที่มันหาได้มาให้เราครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xF5gNxg0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/njhunpz0z4mfhrz48ur5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xF5gNxg0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/njhunpz0z4mfhrz48ur5.png" alt="example-routing.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;จาก &lt;a href="https://zenodo.org/record/1247385#.X5QTB50zZD8"&gt;https://zenodo.org/record/1247385#.X5QTB50zZD8&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ลองมาดูตัวอย่างที่ให้ความรู้สึกเป็น AI มากขึ้นหน่อยดีกว่าครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MarI/O bot เล่นเกม Mario&lt;/strong&gt; &lt;br&gt;
ความน่าสนใจของงานนี้อยู่ตรงที่ว่า คนสร้างไม่ได้สอนตัว AI เลยแม้แต่น้อย ว่ากดปุ่มอะไรแล้วจะเกิดอะไร หรือกลไกของเกมส์ Mario เป็นอย่างไร หรือโดนโจมตี/เก็บของแล้วจะเป็นอะไร นั่นคือเริ่มต้นมา AI ไม่รู้อะไรเลย แต่มันจะค่อย ๆ ลองเล่น ลองผิดลองถูกไปเรื่อย ๆ จนเก่งขึ้นตามภาพครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iMyUZpTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x4rpwnmqvxxhbzbb512i.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iMyUZpTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x4rpwnmqvxxhbzbb512i.gif" alt="example-mario.gif"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;จาก &lt;a href="https://www.youtube.com/watch?v=qv6UVOQ0F44"&gt;https://www.youtube.com/watch?v=qv6UVOQ0F44&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;เบื้องหลังความเทพของ bot ตัวนี้ก็คือ &lt;strong&gt;Genetic Algorithm&lt;/strong&gt; นี่แหละ เอาไปรวมกับ &lt;strong&gt;Neural Network&lt;/strong&gt; เพื่อให้มันสามารถอ่าน input จาก pixel บนหน้าจอ แล้วทำหน้าที่เป็นเหมือนกับสมองในการสั่งการตัว Mario ว่าเมื่อเห็นภาพบนหน้าจอแบบนี้ แล้วจะต้องกดปุ่มอะไรต่อ&lt;/p&gt;

&lt;p&gt;การนิยามโจทย์สำหรับ GA ก็จะเป็นว่า &lt;strong&gt;คำตอบ (solution) ที่ต้องการก็คือ โครงสร้างของ Neural Network ที่สามารถควบคุม Mario ได้ดีที่สุด&lt;/strong&gt; ทำให้ Mario สามารถเดินทางไปได้ &lt;strong&gt;เป็นระยะทางไกลที่สุด (objective value)&lt;/strong&gt; นั่นเองครับ&lt;/p&gt;

&lt;p&gt;แล้วก็ยังมีตัวอย่างน่าสนใจอีกมาก ที่ GA สามารถทำได้ ถ้าสนใจลองหาเพิ่มเติมได้ครับ&lt;br&gt;
keywords: &lt;code&gt;genetic algorithm&lt;/code&gt;, &lt;code&gt;evolutionary algorithm&lt;/code&gt;&lt;br&gt;
อันนี้ลองหาใน Youtube มาไว ๆ ครับ&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/zIkBYwdkuTk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Aut32pR5PQA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/W15wZaWj6hk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;จะเห็นได้ว่า GA สามารถเอาไปประยุกต์ใช้กับปัญหาได้หลากหลายมาก ถ้าเราสามารถนิยามปัญหาของเราเป็นการหาคำตอบที่ดีที่สุด (เท่าที่จะหาได้) จากบรรดาคำตอบที่เป็นไปได้ทั้งหมด (search space) เช่น ใช้ต้นทุนน้อยสุด หรือให้ค่าความเหมาะสม (fitness) มากที่สุด&lt;/p&gt;

&lt;h1&gt;
  
  
  Genetic Algorithm ทำงานอย่างไร
&lt;/h1&gt;

&lt;p&gt;ทีนี้ แล้วอะไรล่ะที่ทำให้ GA มันเทพขนาดนั้น ถึงขนาดนำมาควบคุม Mario หรือช่วยสายลับลูกชิ้นทอดของเราได้เลย ...เริ่มจากมาทำความเข้าใจหลักการทำงานของ algorithm กันก่อนนะครับ&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Genetic Algorithm (GA) จะใช้แนวคิดคล้าย ๆ กับการวิวัฒนาการของสิ่งมีชีวิตเลยครับ คือเป็น Natural Selection สิ่งมีชีวิตที่อ่อนแอจะถูกกำจัด ตัวที่แข็งแกร่งจะอยู่รอด และได้สืบพันธุ์ต่อไป แล้วก็จะมีกลไกของการกลายพันธุ์ (mutate) ของ Gene ทีละนิดในแต่ละครั้งที่สืบพันธุ์ เหมือนเป็นการลองผิดลองถูก คล้าย ๆ กับที่มนุษย์ค่อย ๆ มีการกลายพันธุ์/วิวัฒนาการ ให้หางของเราหายไปทีละนิดจนเป็นมนุษย์อย่างในปัจจุบัน คือค่อย ๆ สร้างความแตกต่างออกไปจากพ่อ-แม่ ของมัน เพื่อสร้างความหลากหลายครับ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;มาทบทวนวิชาชีวะมัธยมกันสักหน่อยนะครับ 555&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  โครโมโซม (Chromosome)
&lt;/h3&gt;

&lt;p&gt;โดยสิ่งมีชีวิตแต่ละตัวจะมี &lt;strong&gt;สารพันธุกรรม (Chromosome)&lt;/strong&gt; อยู่ครับ สิ่งนี้จะเป็นตัวกำหนดลักษณะของมัน ซึ่งสิ่งนี้ สำหรับใน GA ก็คือตัว &lt;strong&gt;คำตอบ&lt;/strong&gt; ที่เราจะให้มันหานั่นเองครับ เราอยากให้ Algorithm มันค่อย ๆ พัฒนา Chromosome ของมันไปเรื่อย ๆ จนในท้ายที่สุดจะได้ Chromosome ที่แข็งแกร่งที่สุดที่อยู่รอดออกมาเป็นคำตอบ ก็คือ Chromosome ที่ทำให้ได้ cost ต่ำที่สุด หรือมีค่า fitness สูงสุด ตามที่เราคุยกันไปตอนต้นนั่นเองครับ&lt;/p&gt;

&lt;p&gt;ถ้าเป็นโจทย์การหาเส้นทางฯ Chromosome 1 อันก็อาจจะเป็น&lt;br&gt;
&lt;strong&gt;ลำดับของการเดินทาง 1 รอบ [คลังสินค้า, ร้าน1, ร้าน2, คลังสินค้า, ร้าน3]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;หรือถ้าเป็นโจทย์เกมส์ MarI/O Chromosome 1 อันก็อาจจะเป็น &lt;strong&gt;โครงสร้าง Neural Network แบบหนึ่ง&lt;/strong&gt; อย่างนี้เป็นต้น&lt;/p&gt;
&lt;h3&gt;
  
  
  การคัดเลือกโดยธรรมชาติ (Natural Selection)
&lt;/h3&gt;

&lt;p&gt;ทีนี้ในการทำ GA เราก็จะมีกลไกในการคัดเลือกตัวที่เก่งที่สุดอยู่ครับ โดยเราสร้าง &lt;strong&gt;ประชากร&lt;/strong&gt; ของสิ่งมีชีวิตขึ้น แบ่งเป็น &lt;em&gt;รุ่น (generation)&lt;/em&gt; เริ่มจากรุ่น 0 หรือ generation 0 เราจะสร้าง Chromosome ขึ้นมาเยอะ ๆ แบบสุ่มมั่ว ๆ เลยนะครับ สมมุติว่าสร้างมา 500 Chromosomes โดย Chromosome แต่ละอันก็จะแทนสิ่งมีชีวิต 1 ตัว ที่เกิดมาแบบสุ่ม อาจจะมีทั้งตัวที่แข็งแกร่ง และตัวที่อ่อนแอปน ๆ กันอยู่&lt;/p&gt;

&lt;p&gt;จากนั้นเราก็จะทำการคำนวนครับว่า Chromosome แต่ละอันมีความแข็งแกร่ง หรือ &lt;strong&gt;ความเหมาะสม (Fitness)&lt;/strong&gt; มากแค่ไหน ออกมาเป็นตัวเลข 1 ตัวสำหรับแต่ละอัน เช่น Mario ตัวนี้เดินไปได้ไกล 50 เมตร แบบนี้เป็นต้น&lt;/p&gt;

&lt;p&gt;ทีนี้พอเรารู้แล้วว่าตัวไหนเก่งเท่าไหร่ เราก็จะทำการกำจัดตัวที่อ่อนแอออกไปครับ เช่นถ้าเราบอกว่าเราต้องการ Fitness ให้สูงที่สุด ตัวที่อ่อนแอของเราก็คือตัวที่ Fitness ต่ำ ๆ เราก็จะกำจัดตัวที่ Fitness ต่ำ ๆ ออกไปส่วนหนึ่ง อาจจะบอกว่าเอาออกสัก 50% ของประชากรทั้งหมด หรือมากน้อยกว่านั้นก็ได้&lt;/p&gt;

&lt;p&gt;พอเรากำจัดตัวที่อ่อนแอออกไปแล้ว เราก็จะให้ตัวที่เหลือรอดเนี่ย ได้มาทำการ &lt;strong&gt;สืบพันธุ์&lt;/strong&gt; เพื่อสร้างลูกหลานและส่งต่อ Chromosome ที่ดีต่อไปครับ (เดี๋ยวจะอธิบายในส่วนของการสืบพันธุ์เพิ่มเติมครับ)&lt;/p&gt;

&lt;p&gt;แล้วเราก็จะทำอย่างนี้ไปเรื่อย ๆ ใน generation ต่อ ๆ ไป ทำให้ Chromosome หรือคำตอบที่ดีแต่ละอันที่สุ่มมาได้เกิดการผสมกันไปมา ถ้าผสมแล้วไม่ดีก็จะถูกกำจัดออก แล้วเหลือไว้แต่อันที่ดี เพื่อไปผสมกันให้ดีขึ้นไปอีกเรื่อย แล้วสุดท้ายคำตอบที่ดีที่สุดที่เราได้จาก Algorithm ก็คือ Chromosome ที่ให้ค่า Fitness ดีที่สุดใน &lt;strong&gt;generation สุดท้าย&lt;/strong&gt; นั่นเองครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qbShlG4H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ire19k55210d60w3iutb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qbShlG4H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ire19k55210d60w3iutb.png" alt="natural-selection.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพการเกิด Natural Selection โดยยีราฟตัวที่มี Chromosome คอสั้นจะโดนแย่งอาหาร และค่อย ๆ อดอาหารตายไป ทำให้ยีราฟที่มี Chromosome คอยาวอยู่รอด และสืบพันธุ์ส่งต่อ "ความคอยาว" ให้รุ่นลูกหลานถัดไป จนยีราฟทุกตัวใน generation หลังก็จะคอยาวกันหมด จาก &lt;a href="https://www.toppr.com/content/story/amp/natural-selection-and-polymorphism-73211/"&gt;https://www.toppr.com/content/story/amp/natural-selection-and-polymorphism-73211/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  การสืบพันธุ์ และการกลายพันธุ์ (Reproduction and Mutation)
&lt;/h3&gt;

&lt;p&gt;การสืบพันธุ์ (Reproduction) ถ้าเป็นตามที่เราเรียนมาก็คือเราจะเอา Chromosome จากพ่อกับแม่ มา crossover กัน โดยสลับหน่วยย่อยของ Chromosome กันบางส่วนเพื่อแลกเปลี่ยนสารพันธุกรรมกัน ก่อนจะส่งต่อไปให้กับรุ่นลูกใช่ไหมครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--73hYFUYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qajsakrvl1hfjq28m8pn.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--73hYFUYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qajsakrvl1hfjq28m8pn.jpeg" alt="crossover.jpeg"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Chromosomes Crossing Over จาก &lt;a href="https://ib.bioninja.com.au/standard-level/topic-3-genetics/33-meiosis/crossing-over.html"&gt;https://ib.bioninja.com.au/standard-level/topic-3-genetics/33-meiosis/crossing-over.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ใน Genetic Algorithm เราก็จะทำเช่นเดียวกันครับ คือเราเอาตัวที่แข็งแกร่ง 2 ตัวมาทำการแลกเปลี่ยนบางส่วนของคำตอบกัน โดยคาดหวังว่ามันอาจจะได้ Combination ที่ดีขึ้นออกมาในรุ่นลูกครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EWaEv2d2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ilozhusqghonh3r9bj3o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EWaEv2d2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ilozhusqghonh3r9bj3o.jpg" alt="reproduction.jpg"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพการ cross chromosomes แบบหนึ่ง ใน GA จาก &lt;a href="https://www.tutorialspoint.com/genetic_algorithms/genetic_algorithms_crossover.htm"&gt;https://www.tutorialspoint.com/genetic_algorithms/genetic_algorithms_crossover.htm&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;จากนั้นโดยทั่วไป เขาก็ใส่จะกลไกการกลายพันธุ์เข้าไปเล็กน้อย เช่นลองแก้ค่าบางค่าของ Chromosome จาก 0 เป็น 1 ดูแบบสุ่ม หรือลองสลับค่าบางค่าดู เป็นต้น เพื่อทำให้เกิดความหลากหลายของ Chromosome มากขึ้นครับ ก็เหมือนกับการลองผิดลองถูก คือแก้แล้วอาจจะทำให้คำตอบดีขึ้นหรือแย่ลงก็ได้ แต่ยิ่งลองผิดลองถูกมากก็ยิ่งมีโอกาสเจอคำตอบที่ดีมากขึ้นใช่ไหมครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hG3ORcLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mgssre2w3cs7jvn6e8ko.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hG3ORcLu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mgssre2w3cs7jvn6e8ko.jpg" alt="mutation-swap.jpg"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพ Swap Mutation จาก &lt;a href="https://www.tutorialspoint.com/genetic_algorithms/genetic_algorithms_mutation.htm"&gt;https://www.tutorialspoint.com/genetic_algorithms/genetic_algorithms_mutation.htm&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  สรุปกระบวนการ Genetic Algorithm
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;เริ่มจากสร้างประชากร Chromosomes ขึ้นมาแบบสุ่ม สมมุติ 500 ตัว&lt;/li&gt;
&lt;li&gt;คำนวนค่า Cost หรือ Fitness ของ Chromosome แต่ละตัว&lt;/li&gt;
&lt;li&gt;ถ้าเงื่อนไขในการหยุดเป็นจริง ให้หยุดทำงาน ถ้ายังไม่จริงก็ทำต่อ&lt;/li&gt;
&lt;li&gt;กำจัดตัวที่อ่อนแอออกไป สมมุติครึ่งหนึ่ง ก็จะเหลือตัวที่แข็งแกร่งอยู่ 250 ตัว&lt;/li&gt;
&lt;li&gt;เอาตัวที่เหลืออยู่มาสืบพันธ์และสุ่มกลายพันธุ์ เพื่อเพิ่มจำนวนของประชากรให้ Chromosomes ทั้งหมดกลับมามีจำนวน 500 เท่าเดิม&lt;/li&gt;
&lt;li&gt;ทำซ้ำตั้งแต่ข้อ 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_qzy1aa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xtfrtm512jyhv22aco7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_qzy1aa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xtfrtm512jyhv22aco7c.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;จาก &lt;a href="https://www.researchgate.net/figure/Genetic-algorithm-flowchart_fig2_263224226"&gt;https://www.researchgate.net/figure/Genetic-algorithm-flowchart_fig2_263224226&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ผมละรายละเอียดบางส่วนเอาไว้ คิดว่าไปดูในโค้ดน่าจะเห็นภาพมากกว่าครับ&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  แล้วจะขายลูกชิ้นที่ไหนดี...
&lt;/h1&gt;

&lt;p&gt;ก่อนที่ภาคีลูกชิ้นทอดของเราจะรอนานไปมากกว่านี้ ถ้าพอจะเข้าใจแนวคิดของ Genetic Algorithm คร่าว ๆ แล้ว เรามาเริ่มเขียนโค้ดเพื่อช่วยองค์กรหาตำแหน่งขายลูกชิ้นกันดีกว่าครับ&lt;/p&gt;

&lt;p&gt;โค้ดเต็ม ๆ ทั้งหมดดูได้ใน Google Colab ตามลิ้งนี้เลยครับ สามารถเปิดขึ้นลองรันดู ไปพร้อม ๆ กับอ่านบทความได้เลยครับ &lt;a href="https://colab.to/1K6qosp_HcGmgBy8dVqSlpooYLBSeEnbA"&gt;https://colab.to/1K6qosp_HcGmgBy8dVqSlpooYLBSeEnbA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ก่อนอื่นกำหนดค่าคงตัวต่าง ๆ กันก่อน&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;POPULATION_SIZE:&lt;/em&gt; ให้ในหนึ่ง Generation มีประชากรเป็น &lt;strong&gt;500 chromosomes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;N_SHOPS:&lt;/em&gt; ให้ Chromosome 1 ตัว หรือก็คือ คำตอบ 1 คำตอบที่เราต้องการหา ประกอบด้วยตำแหน่ง xy ของร้านค้า จำนวน &lt;strong&gt;10 ร้านค้า&lt;/strong&gt; เพราะเรามีสายลับทั้งหมด 10 คน&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;EFFECT_RADIUS:&lt;/em&gt; ให้รัศมีของกลิ่นหอมกระจายไปไกล เป็นระยะทาง &lt;strong&gt;1.0 หน่วย&lt;/strong&gt; (Euclidean Distance = 1.0)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CAPACITY:&lt;/em&gt; ให้ร้านค้า 1 ร้าน สามารถขายให้ได้แค่ &lt;strong&gt;ไม่เกิน 150 คนเท่านั้น&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ข้อมูลที่อยู่ของคนในม็อบ
&lt;/h2&gt;

&lt;p&gt;เนื่องจากเราไม่มีข้อมูลจริง ก็จะเริ่มจากสร้างข้อมูลตำแหน่งของคนในม็อบขึ้นมาก่อนได้เป็นแบบนี้นะครับ สมมุติว่า 1 จุดแทน 1 คนยืนอยู่เลย จะมีอยู่ 3 กลุ่ม clusters ขนาดเล็ก กลาง ใหญ่ รวมทั้งหมด 1,250 คนครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TdfgZGvT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k8jtzfour2u3gd3dmmf4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TdfgZGvT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/k8jtzfour2u3gd3dmmf4.png" alt="colab-mobdata.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: รายละเอียดว่าสร้างออกมาแบบนี้ได้ยังไงไม่ได้น่าสนใจมากขออนุญาตข้ามนะครับ สามารถไปดูได้ในโค้ดเต็มครับ&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  ฟังก์ชันต่าง ๆ
&lt;/h2&gt;

&lt;p&gt;สำหรับ project นี้ไม่ได้ซับซ้อนมากนัก ผมจะขอเริ่มแบบง่าย ๆ โดยการ implement logic ของส่วนต่าง ๆ ที่ยุ่ง ๆ ให้เป็นฟังก์ชันย่อย ๆ  ออกมาตามนี้ครับ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ฟังก์ชัน &lt;em&gt;get_customers(positions, mob)&lt;/em&gt; สำหรับรับตำแหน่งของร้านค้าและม็อบ เพื่อไปคำนวณว่าเมื่อตั้งร้านตามตำแหน่งต่อไปนี้แล้ว คนไหนบ้างในม็อบจะมาซื้อ และเป็นจำนวนเท่าไหร่ในแต่ละร้าน&lt;/li&gt;
&lt;li&gt;ฟังก์ชัน &lt;em&gt;create_initial_population()&lt;/em&gt; สำหรับสุ่มสร้าง population ของ chromosomes เริ่มต้น&lt;/li&gt;
&lt;li&gt;ฟังก์ชัน &lt;em&gt;calculate_objective(mob, population)&lt;/em&gt; สำหรับคำนวณค่า fitness&lt;/li&gt;
&lt;li&gt;ฟังก์ชัน &lt;em&gt;reproduce(parents, count)&lt;/em&gt; สำหรับนำ population ที่ผ่านการคัดเลือกมาเป็น parents เพื่อทำการสืบพันธุ์และกลายพันธุ์ต่อไป&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;get_customers(positions, mob)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;ฟังก์ชันนี้ใช้สำหรับคำนวณว่าเมื่อตั้งร้านตามตำแหน่ง &lt;code&gt;positions&lt;/code&gt; แล้วใครในม็อบจะมาซื้อบ้าง (เลข index ของ array mob) และได้จำนวนการขายของแต่ละร้านเท่าไหร่ โดยที่ &lt;code&gt;mob&lt;/code&gt; คือ array ของตำแหน่ง (x, y) ของคนในม็อบ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w5nsNCne--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hllpjzqwm9byp1ao281r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w5nsNCne--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hllpjzqwm9byp1ao281r.png" alt="colab-script-get_customers.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ผมวนลูปเพื่อเอาตำแหน่งของร้านค้า &lt;code&gt;p&lt;/code&gt; มาทีละร้าน จาก &lt;code&gt;positions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;คำนวณระยะห่างของแต่ละคนในม็อบกับร้าน &lt;code&gt;p&lt;/code&gt; (Euclidean Distance)&lt;/li&gt;
&lt;li&gt;เลือกคนที่อยู่ใกล้ที่สุดมาจำนวนไม่เกิน 150 คน (เท่ากับจำนวนคนที่ร้าน 1 ร้านสามารถขายให้ได้ &lt;em&gt;CAPACITY = 150&lt;/em&gt;) และอยู่ห่างจากร้าน &lt;code&gt;p&lt;/code&gt; เป็นระยะทางไม่เกิน 1.0 หน่วย (คือระยะทางที่กลิ่นหอมจากร้าน &lt;code&gt;p&lt;/code&gt; สามารถลอยไปได้ไกลที่สุด &lt;em&gt;EFFECT_RADIUS = 1.0&lt;/em&gt;) เราจะนับไปเลยว่าคนพวกนี้จะเข้ามาซื้อร้าน &lt;code&gt;p&lt;/code&gt; เพราะไม่มีใครทนต่อกลิ่นหอมได้&lt;/li&gt;
&lt;li&gt;นับจำนวนของคนที่ผ่านเงื่อนไขในข้อ 3 แล้วเพิ่มไปใน customer_counts ของร้าน &lt;code&gt;p&lt;/code&gt; เพื่อใช้บอกว่าร้าน &lt;code&gt;p&lt;/code&gt; ได้ลูกค้าไปจำนวนกี่คน&lt;/li&gt;
&lt;li&gt;ลบคนที่เป็นลูกค้าของร้าน &lt;code&gt;p&lt;/code&gt; ไปแล้วออกจาก mob นั่นคือเราให้คนคนหนึ่งซื้อลูกชิ้นทอดได้แค่ครั้งเดียว จากร้านเดียวเท่านั้น&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;หลังจบการทำงาน ฟังก์ชัน &lt;em&gt;get_customers(positions, mob)&lt;/em&gt; จะ return output ออกมา 2 อย่าง คือ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;customer_indexes:&lt;/code&gt; เป็น array 1 มิติ เก็บ index ของคนในม็อบ ที่ได้ซื้อลูกชิ้นทอดจากร้านใดร้านหนึ่งไป&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customer_counts:&lt;/code&gt; เป็น array 1 มิติ เป็นจำนวนของลูกค้าที่เข้าไปซื้อลูกชิ้นทอดจากร้านแต่ละร้าน มีขนาดเท่ากับจำนวนร้านค้าที่ใส่เข้าไป (&lt;code&gt;positions&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;เหตุผลที่ผมเริ่มทำฟังก์ชันนี้ก่อน ก็เพื่อให้สามารถเขียนฟังก์ชัน &lt;em&gt;report(mob, positions)&lt;/em&gt; ที่จะ report performance ของแต่ละร้านใน &lt;code&gt;positions&lt;/code&gt; ว่าขายได้เท่าไหร่ และ plot ออกมาเป็นกราฟให้ดูง่าย จะได้เขียนโค้ดให้เห็นภาพได้มากขึ้นครับ&lt;/p&gt;

&lt;p&gt;ทดสอบฟังก์ชัน report()&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ieAHOyp6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zz0v51o1w883dhr64vrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ieAHOyp6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zz0v51o1w883dhr64vrv.png" alt="colab-report-sample.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;จุดสีน้ำเงิน&lt;/em&gt; คือคนที่ยังไม่ได้เป็นลูกค้าร้านใด&lt;br&gt;
&lt;em&gt;จุดสีเขียว&lt;/em&gt; คือคนที่เป็นลูกค้าซื้อไปเรียบร้อยแล้ว&lt;br&gt;
&lt;em&gt;กากบาทสีแดง&lt;/em&gt; คือตำแหน่งร้านค้าที่ input เข้าไป (&lt;code&gt;positions&lt;/code&gt;)&lt;br&gt;
&lt;em&gt;วงกลมสีแดง&lt;/em&gt; คือรัศมีของกลิ่นหอมแต่ละร้าน (มีรัศมีเท่ากับ &lt;em&gt;EFFECT_RADIUS = 1.0&lt;/em&gt;)&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;create_initial_population()&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;ฟังก์ชันนี้ใช้สำหรับสร้าง population ของ Chromosomes เริ่มต้นโดยสุ่มค่า xy ขึ้นมา การ implement ก็ค่อนข้างง่าย ก็คือวนลูปเป็นจำนวนเท่ากับจำนวนของประชากรที่เราอยากได้ ซึ่งผมกำหนดไว้เป็น 500 ตัว (&lt;em&gt;POPULATION_SIZE = 500 Chromosomes&lt;/em&gt;) สำหรับ Chromosome แต่ละตัว ก็จะทำการสุ่มตำแหน่งพิกัด xy ของร้าน 10 ร้าน (&lt;em&gt;N_SHOPS = 10&lt;/em&gt;) ออกมา แทนคำตอบ 1 คำตอบ โดยใช้ฟังก์ชัน &lt;code&gt;np.random.uniform()&lt;/code&gt; ของ library NumPy ในการสุ่มเลขแบบ uniform&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0Xpzz1Ek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3jvlupl08fjsrtxhme3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Xpzz1Ek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3jvlupl08fjsrtxhme3g.png" alt="colab-script-create_initial_population.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตัวอย่าง Chromosomes 5 ตัวแรกใน &lt;code&gt;population&lt;/code&gt; ก็คือเป็น array ของ (x, y) ทั้งหมด 10 ตัว&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xNzCdahw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zc44o37ybsmnbhf5p963.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xNzCdahw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zc44o37ybsmnbhf5p963.png" alt="colab-population-sample.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ลองเอา population ที่สร้างได้ ตัวที่ 0 กับตัวที่ 1 มา plot ดู ก็จะเห็นว่า แต่ละตัวมีตำแหน่งของร้านจำนวน 10 ร้าน และกระจายกันแบบมั่ว ๆ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6SYi4FXC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x6yh9oe35oxpn3d559lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6SYi4FXC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x6yh9oe35oxpn3d559lo.png" alt="colab-chromosomes-sample.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;calculate_objective(mob, population)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;ฟังก์ชันนี้ใช้สำหรับคำนวณค่า Fitness ของ Chromosome แต่ละตัวจากใน population ซึ่ง Fitness ของ Chromosome แต่ละตัว ก็คือยอดขายรวมของแต่ละร้านใน Chromosome นั้นนั่นเอง&lt;/p&gt;

&lt;p&gt;เนื่องจากเรามีฟังก์ชัน &lt;em&gt;get_customers()&lt;/em&gt; ที่สามารถคำนวณ &lt;code&gt;customer_counts&lt;/code&gt; ของร้านค้าแต่ละร้านใน 1 Chromosome ได้อยู่แล้ว จึงสามารถเอามาใช้ได้ตามนี้ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cN2qmyyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i35qqm0cz8suztf3o2db.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cN2qmyyN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i35qqm0cz8suztf3o2db.png" alt="colab-script-calculate_objective.png&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ไป ๆ มา ๆ เหมือนว่าฟังก์ชันที่ยากที่สุดจะเป็น &lt;em&gt;get_customers()&lt;/em&gt; นี่แหละครับ 555&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;reproduce(parents, count)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;สุดท้ายก็คือฟังก์ชัน &lt;em&gt;reproduce()&lt;/em&gt; ครับ โดยฟังก์ชันนี้มี 2 ขั้นตอนคือขั้นตอนการสุ่ม crossover chromosomes ทีละคู่ และขึ้นตอนของการสุ่มกลายพันธุ์ ผมจึงสร้าง 2 ขั้นตอนนั้นออกมาเป็นอีก 2 ฟังก์ชันย่อย ๆ คือ &lt;em&gt;cross_chromosome(a, b)&lt;/em&gt; และ &lt;em&gt;mutate_population(population, prob, scale)&lt;/em&gt; ตามนี้ครับ&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;cross_chromosome(a, b)&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;สุ่มตำแหน่งที่จะทำการ cross โดยใช้ &lt;code&gt;np.random.choice()&lt;/code&gt; โดยผลลัพธ์จะออกมาเป็น array ของค่า Boolean True หรือ False อย่างใดอย่างหนึ่ง โดยในตำแหน่งร้านที่ &lt;code&gt;will_cross&lt;/code&gt; เป็น True ก็จะถูกสลับระหว่าง chromosome a และ chromosome b&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pz0r1rsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vqfn4uglpth9z7oj1sx5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pz0r1rsI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vqfn4uglpth9z7oj1sx5.png" alt="colab-script-cross_chromosome.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตัวอย่างผลลัพธ์การ cross ระหว่าง chromosome a และ b&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WkDTJi7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/apzsu7rlwpmcxnbvkmp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WkDTJi7b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/apzsu7rlwpmcxnbvkmp9.png" alt="colab-crossing-sample.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;mutate_population(population, prob, scale)&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;ในส่วนของการกลายพันธุ์ ผมใช้แบบง่าย ๆ คือเป็นการเลื่อนตำแหน่ง (x, y) ของร้านค้าแบบสุ่ม&lt;br&gt;
ขั้นแรกผมสุ่มก่อนว่าจะให้เกิดการกลายพันธุ์ที่ Chromosome ตัวไหนบ้างจากใน &lt;code&gt;population&lt;/code&gt; โดยมีความน่าจะเป็นของการกลายพันธุ์เท่ากับ &lt;code&gt;prob&lt;/code&gt; (&lt;em&gt;default &lt;code&gt;prob&lt;/code&gt; = 0.2&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;จากนั้นก็สุ่ม &lt;code&gt;noise&lt;/code&gt; ขึ้นมา คือเป็นค่าสุ่มเล็ก ๆ ที่จะเอาไปบวกกับ (x, y) ของร้านใน &lt;code&gt;population&lt;/code&gt; โดยใช้ &lt;code&gt;np.random.normal&lt;/code&gt; ที่จะสุ่มค่าตัวเลขออกมาจาก normal distribution โดยใช้ &lt;code&gt;scale&lt;/code&gt; = 0.3 (ยิ่งเยอะ ยิ่งมีโอกาสที่ค่าที่สุ่มออกมาจะเลขใหญ่ ๆ) โดย &lt;code&gt;noise&lt;/code&gt; ในตำแหน่งที่เราเลือกว่าจะไม่กลายพันธุ์ ก็จะ set ให้เป็น 0 ไป จากนั้นก็เอา &lt;code&gt;noise&lt;/code&gt; ไปบวกเข้ากับ &lt;code&gt;population&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---hOjDRN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uv7mwd2ot7y1kon9k9o1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---hOjDRN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uv7mwd2ot7y1kon9k9o1.png" alt="colab-script-mutate_population.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตัวอย่างของ chromosome ที่ถูกทำให้กลายพันธุ์ จะเห็นว่าบางร้านก็ถูกเลื่อนออกไปไกลจากเดิม แต่บางร้านก็ไม่ไกล คละกันไป&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vo87XJOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n42ephenap0x1ic0iv2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vo87XJOL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n42ephenap0x1ic0iv2j.png" alt="colab-mutation-sample.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  ฟังก์ชันหลัก &lt;em&gt;reproduce(parents, count)&lt;/em&gt;
&lt;/h4&gt;

&lt;p&gt;เมื่อเราได้ฟังก์ชันย่อยทั้งสองครบแล้ว ตอนนี้เราก็สามารถเขียน &lt;em&gt;reproduce()&lt;/em&gt; ได้ดังนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iWghW9YX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/03r43u4wh32xytyo63z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iWghW9YX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/03r43u4wh32xytyo63z6.png" alt="colab-script-reproduce.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ก็คือวนลูปเพื่อสุ่มหยิบ chromosomes มาทีละคู่ จาก &lt;code&gt;parents&lt;/code&gt; เอามา cross กันด้วย &lt;em&gt;cross_chromosome()&lt;/em&gt; จากนั้นก็เก็บผลลัพธ์ไว้ใน &lt;code&gt;children&lt;/code&gt; จนได้จำนวนตามที่ต้องการคือเท่ากับ &lt;code&gt;count&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;จากนั้นก็เอา &lt;code&gt;children&lt;/code&gt; ไปสุ่มกลายพันธุ์ด้วย &lt;em&gt;mutate_population()&lt;/em&gt; ที่เราสร้างเอาไว้ แล้วก็ return &lt;code&gt;children&lt;/code&gt; ซึ่งเป็น array ของลูกที่ได้กลับไป&lt;/p&gt;

&lt;p&gt;เท่านี้เราก็ได้ฟังก์ชันทั้งหมดที่ต้องการเรียบร้อยแล้ว เขียนไปเขียนมาก็เริ่มยาวแล้วเหมือนกัน 555 ต่อไปก็จะเขียนโค้ดการทำงานหลักที่ใช้หาคำตอบกันครับ&lt;/p&gt;
&lt;h2&gt;
  
  
  เขียนโค้ดการทำงานหลักเพื่อหาคำตอบ...สักที
&lt;/h2&gt;

&lt;p&gt;เมื่อยึดตามกระบวนการทำงานของ Genetic Algorithm ที่เราสรุปเอาไว้ด้านบน เราก็พอจะร่างโครงขึ้นมาเป็น outline ได้ประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. สร้าง population เริ่มต้น
&lt;/span&gt;
&lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. คำนวณ fitness
&lt;/span&gt;    &lt;span class="n"&gt;objective&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. ตรวจสอบเงื่อนไขการหยุดการทำงาน
&lt;/span&gt;
    &lt;span class="c1"&gt;# 4. กำจัดตัวที่อ่อนแอ คัดเลือกตัวที่แข็งแกร่ง
&lt;/span&gt;    &lt;span class="n"&gt;population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;# 5. สืบพันธุ์ และกลายพันธุ์
&lt;/span&gt;    &lt;span class="n"&gt;population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'generation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ทีนี้เราก็เติมโค้ดแต่ละส่วนลงไปด้วยฟังก์ชันที่เราเขียนเอาไว้แล้ว ตามนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ใช้ฟังก์ชัน &lt;em&gt;create_initial_population()&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;ใช้ฟังก์ชัน &lt;em&gt;calculate_objective()&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;...ขออนุญาตข้ามไปก่อน&lt;/li&gt;
&lt;li&gt;ให้ใช้ค่า fitness จากตัวแปร &lt;code&gt;objective&lt;/code&gt; ในการเรียงลำดับจากมากไปน้อย แล้วตัดให้เหลือแค่ครึ่งแรก ที่มีค่า fitness สูง&lt;/li&gt;
&lt;li&gt;ใช้ฟังก์ชัน &lt;em&gt;reproduce()&lt;/em&gt; ได้ &lt;code&gt;children&lt;/code&gt; ออกมา จากนั้นเอา &lt;code&gt;children&lt;/code&gt; ไปรวมกับ &lt;code&gt;population&lt;/code&gt; ที่เหลืออยู่&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ได้เป็นโค้ดที่สมบูรณ์มากขึ้นดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. สร้าง population เริ่มต้น
&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_initial_population&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. คำนวณ fitness
&lt;/span&gt;    &lt;span class="n"&gt;objective&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_objective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;population&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. ตรวจสอบเงื่อนไขการหยุดการทำงาน
&lt;/span&gt;
    &lt;span class="c1"&gt;# 4. กำจัดตัวที่อ่อนแอ คัดเลือกตัวที่แข็งแกร่ง
&lt;/span&gt;    &lt;span class="n"&gt;rank_indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POPULATION_SIZE&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;population&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rank_indexes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# 5. สืบพันธุ์ และกลายพันธุ์
&lt;/span&gt;    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reproduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;population&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;POPULATION_SIZE&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;population&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'generation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ซึ่งจริง ๆ โค้ดด้านบนนี้ก็สามารถรันได้จริงแล้วนะครับ ทำงานได้ถูกต้องทุกอย่าง ปัญหาอย่างเดียวก็คือมันไม่รู้ว่าจะต้องหยุดทำงานเมื่อไหร่ ถ้าลองสั่งรันดูมันก็จะทำงานไปเรื่อย ๆ ครับ 555&lt;/p&gt;

&lt;p&gt;ดังนั้นเราต้องเขียนส่วนที่ 3 ที่เราได้ข้ามไป เพื่อบอกมันว่าเมื่อไหร่ถึงจะหยุดได้&lt;br&gt;
ซึ่งจริง ๆ สามารถทำได้หลายวิธีมากครับ เราอาจจะบอกว่าให้มันทำไปเป็นจำนวน 100 generations แล้วหยุดก็ได้&lt;br&gt;
วิธีที่ผมชอบใช้ก็คือ ให้มันทำงานไปเรื่อย ๆ &lt;strong&gt;จนกว่ามันจะไม่สามารถหา chromosome ที่ดีขึ้นได้เป็นระยะเวลาหนึ่ง&lt;/strong&gt; เช่นไม่เพิ่มเป็นจำนวน 10 generation ก็ให้หยุดนั่นเองครับ&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;พูดอีกอย่างก็คือเมื่อเราไม่สามารถหา chromosome ที่ดีขึ้นได้ ภายใน 10 generations ก็ให้หยุดการทำงาน&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;โดยผมจะเพิ่มตัวแปรที่ใช้จำค่า objective ที่ดีที่สุดเอาไว้ ชื่อ &lt;code&gt;max_so_far&lt;/code&gt; และก็มีตัวแปรที่คอยนับ &lt;code&gt;n_not_improve&lt;/code&gt; ถ้า ค่า objective ที่ดีสุดของ generation ปัจจุบัน &lt;em&gt;ไม่ได้ดีกว่า&lt;/em&gt; objective ที่ดีที่สุดที่เราเคยเจอ ตัว &lt;code&gt;n_not_improve&lt;/code&gt; นี้ก็จะบวกค่าเพิ่มไปทีละ 1 ครับ&lt;/p&gt;

&lt;p&gt;ถ้าเจอค่า objective ที่ดีที่สุดอันใหม่ &lt;code&gt;n_not_improve&lt;/code&gt; ก็จะถูก reset กลับเป็น 0 แต่ถ้า &lt;code&gt;n_not_improve&lt;/code&gt; ถึง 10 เมื่อไหร่ นั่นคือมันไม่ได้เกิดการ improve เป็นเวลานานเกินไปแล้ว ก็จะ break ออกจากลูปแล้วจบการทำงานของโปรแกรมครับ&lt;/p&gt;

&lt;p&gt;โค้ดในส่วนที่เพิ่มเข้ามาก็จะเป็นประมาณนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;max_so_far&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'-inf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;n_not_improve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. ตรวจสอบเงื่อนไขการหยุดการทำงาน
&lt;/span&gt;    &lt;span class="n"&gt;max_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;max_local&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_so_far&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;max_so_far&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_local&lt;/span&gt;
        &lt;span class="n"&gt;n_not_improve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;n_not_improve&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n_not_improve&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# ถ้าไม่มี improvement เป็นจำนวน 10 gen
&lt;/span&gt;            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'generation &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;ขออนุญาตลงแบบย่อ เนื่องจากรู้สึกว่าโค้ดเต็ม ๆ ใหญ่ไปหน่อยครับ 55 สามารถดูทั้งหมดจากใน Colab ได้ครับ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;เท่านี้โค้ด Genetic Algorithm ของเราก็สมบูรณ์แล้วครับ ถ้าลองกดรันดู แต่ละ generation มันก็จะ print ค่าของ fitness (ยอดขาย) ของตัวที่เก่งที่สุดใน generation นั้นออกมา ก็จะเห็นว่ามันค่อย ๆ เพิ่มขึ้นทีละนิด แล้วไปหยุด generation 78 ซึ่งได้ยอดขายรวมเป็น 1235 คน จาก 1250 คน คิดเป็น 98.8%!&lt;/p&gt;

&lt;p&gt;จาก report คำตอบที่ดีที่สุด จะเห็นว่าเหลือแค่ 15 คนเท่านั้นนะครับที่ไม่ซื้อลูกชิ้นทอดของเรา 55&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--my5VP_cY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uyqntvxz7fqq81uuqmof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--my5VP_cY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/uyqntvxz7fqq81uuqmof.png" alt="colab-best-solution.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เท่านี้เราก็สามารถช่วยเหลือองค์กรของเราให้แทรกซึมเข้าไปในม็อบได้สำเร็จอย่างมีประสิทธิภาพสูงสุดแล้วครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  ดูพัฒนาการของคำตอบในแต่ละ generation
&lt;/h1&gt;

&lt;p&gt;นอกจากจะได้คำตอบสุดท้ายแล้ว เรายังสามารถแก้โค้ดนิดหน่อยเพื่อให้มันเก็บคำตอบที่ดีที่สุดของแต่ละ generation เอาไว้มา plot เพื่อดูความเร็วของการเข้าใกล้คำตอบสุดท้ายได้ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NZlk5XPR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9irjv7a60nshf08xdehs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NZlk5XPR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9irjv7a60nshf08xdehs.png" alt="colab-evolution.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จะเห็นได้ว่าตั้งแต่สุ่ม population ออกมาใน generation แรก ก็ได้ยอดขายค่อนข้างเยอะอยู่แล้ว อาจจะเป็นเพราะว่าจำนวนประชากร &lt;em&gt;POPULATION_SIZE&lt;/em&gt; ของเราเยอะมาก ทำให้มีโอกาสโชคดีสุ่มแล้วเจอคำตอบดี ๆ แต่แรกแบบนี้ หรือไม่ก็เป็นเพราะว่าข้อมูลตำแหน่งม็อบของเรามันกระจุกมากเกินไปก็ได้ครับ ถ้าสนใจก็สามารถลองแก้โค้ดปรับตัวแปรต่าง ๆ เล่นดูเพิ่มเติม แล้วเอาผลลัพธ์มาแชร์กันได้นะครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;ถ้าอ่านมาจนถึงตรงนี้คิดว่าน่าจะพอเข้าใจเกี่ยวกับ Genetic Algorithm และปัญหาที่สามารถนำไปประยุกต์ใช้ได้ประมาณหนึ่งแล้ว ซึ่งจริง ๆ Genetic Algorithm ที่นำไปใช้กันจริง ๆ อาจจะแตกต่างจากนี้ได้หลายรูปแบบมากครับ แล้วแต่กรณีที่นำไปใช้ แต่แนวคิดหลักก็จะไม่ต่างกันมาก คือใช้การคักเลือกโดยธรรมชาติ (Natural Selection) มีการสืบพันธุ์ (Reproduction) และการกลายพันธุ์ (Mutation) เพื่อค้นหาคำตอบที่ดีที่สุด&lt;/p&gt;

&lt;p&gt;ซึ่ง Genetic Algorithm ก็เป็นแค่ algorithm หนึ่งที่ใช้ในการหาคำตอบของปัญหาประเภท Search Problem คือเป็นปัญหาที่ให้ algorithm หาคำตอบ จาก set ของคำตอบที่เป็นไปได้ออกมา ที่ satisfy เงื่อนไขบางอย่างที่เรากำหนดเอาไว้ ซึ่งยังมี algorithm อื่น ๆ อีกมาก ถ้าสนใจศึกษาเพิ่มเติมอาจจะเริ่มจากการ search โดยดูตาม &lt;a href="https://www.tutorialspoint.com/artificial_intelligence/artificial_intelligence_popular_search_algorithms.htm"&gt;link นี้&lt;/a&gt; เป็น guide ก็ได้ครับ &lt;/p&gt;

&lt;p&gt;ถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;



&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>machinelearning</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Python Web Scraping part 5 - วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Wed, 12 Aug 2020 15:05:27 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf</link>
      <guid>https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf</guid>
      <description>&lt;p&gt;บทความนี้คิดว่าจะเป็น part สุดท้าย สำหรับการสอน scraping เบื้องต้นแล้วนะครับ หลังจากนี้ ถ้ายังสนใจกันอยู่ คิดว่าจะพา "ทำโจทย์จริง" กันดู อาจจะเป็นบทความแบบนี้อีก หรืออาจจะมาเป็นคลิป เดี๋ยวจะประกาศให้ทราบนะครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ก่อนอื่น ขอทบทวนเนื้อหา parts ก่อน ๆ สักเล็กน้อยนะครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;part 1:&lt;/strong&gt;&lt;/em&gt; เราพูดถึงภาพหยาบ ๆ ของการ scrape ไว้ ว่าเราอาจจะมองได้เป็น &lt;strong&gt;2 ขั้นตอน&lt;/strong&gt; ก็คือ &lt;em&gt;การ scrape&lt;/em&gt; คือดึงข้อมูลดิบ ๆ ลงมาให้ได้ และ &lt;em&gt;การ extract&lt;/em&gt; คือการสกัดส่วนที่เราสนใจ ออกมาจากข้อมูลดิบนั้น&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;part 2:&lt;/strong&gt;&lt;/em&gt; ก็จะพูดถึงเรื่องของการ extract ข้อมูลก่อน โดยจะแนะนำเครื่องมือที่ช่วยให้สามารถ &lt;strong&gt;ตรวจดูโครงสร้างของหน้า web&lt;/strong&gt; ได้แบบไว ๆ เพื่อหาวิธีสกัดข้อมูลที่เราต้องการออกมา&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;part 3:&lt;/strong&gt;&lt;/em&gt; จะสอนการสกัดข้อมูลด้วย &lt;strong&gt;XPath&lt;/strong&gt; เบื้องต้น โดยใช้ประโยชน์จากโครงสร้างของหน้า web ที่เราได้เรียนรู้มา&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;part 4:&lt;/strong&gt;&lt;/em&gt; จะเล่าเกี่ยวกับส่วน scrape บ้าง และได้แนะนำ 7 เทคนิค ที่ช่วยให้เราสามารถ &lt;strong&gt;ดึงข้อมูลจากหลาย ๆ เว็บ&lt;/strong&gt; &lt;em&gt;ที่มีระบบซับซ้อน หรือมีกลไกการป้องกันการ scrape เอาไว้&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;เช่นเดียวกับ part ที่แล้ว ในบทความนี้ เนื้อหาก็จะยังอยู่กับส่วนของ &lt;em&gt;การดึงข้อมูล&lt;/em&gt; ครับ โดยจะแนะนำเครื่องมืออีกชิ้นที่ช่วยให้เราสามารถเข้าใจการรับส่งข้อมูลระหว่างหน้าเว็บของเป้าหมาย กับตัว server ได้ โดยไม่ต้องไปตามอ่านโค้ดของเว็บทุกบรรทัดให้เสียเวลา&lt;/p&gt;

&lt;p&gt;อธิบายก่อนว่า อย่างเช่นตัวอย่างใน part ที่ 1 เว็บเป้าหมายของเราเป็นเว็บ Wikipedia ใช่ไหมครับ ซึ่งเว็บ &lt;em&gt;Wikipedia&lt;/em&gt; เนี่ยไม่ได้ซับซ้อนมากนัก &lt;strong&gt;ข้อมูลทุกอย่างแสดงอยู่บนหน้าเว็บตั้งแต่ต้นแล้ว เราก็สามารถที่จะดึงทั้งหน้านั้นมาตรง ๆ ได้เลย&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;แต่ในเว็บไซต์ส่วนใหญ่ไม่ได้เป็นแบบนั้นครับ&lt;/strong&gt; หลาย ๆ เว็บก็จะมีกลไกในการโหลดข้อมูลมาแสดงเพิ่มเติมทีหลังได้อีก เช่น ต้องกดปุ่มก่อนแล้วข้อมูลถึงจะถูกโหลดขึ้นมาแสดง หรือต้องเลื่อนหน้าจอ (scroll) ลงไปล่างสุด แล้วจะโหลดเนื้อหามาแสดงเพิ่มทีหลัง ดังนั้นถ้าเราโหลดเฉพาะหน้าเว็บของมันมาตรง ๆ แบบตอน part ที่ 1 ก็อาจจะได้มาแค่หน้าว่าง ๆ ที่ยังไม่มีข้อมูล ...แบบนี้จะทำอย่างไร?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fO9r9p05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jxsmnm1o6qfpiuedguy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fO9r9p05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jxsmnm1o6qfpiuedguy3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ตัวอย่าง: เว็บ Pantip จะค่อย ๆ โหลดข้อมูลมาเพิ่มเมื่อ scroll ลง&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;วิธีการหนึ่งที่ทำได้คือเราก็ต้องเข้าไปดูว่า ข้อมูลที่มันโหลดมาเพิ่มเนี่ย มันโหลดมาจากไหน โหลดมาอย่างไร จากนั้นเราก็แค่เลียนแบบมันก็จบ! ในบทความนี้จึงจะมาแนะนำ &lt;strong&gt;Network Inspector&lt;/strong&gt; เครื่องมือชิ้นหนึ่งใน &lt;strong&gt;Google Chrome DevTools&lt;/strong&gt; สำหรับการตรวจสอบการโหลดข้อมูลให้ได้รู้จักกันครับ&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ถ้ายังจำกันได้ใน part ที่ 2 เราก็ได้พูดถึงเครื่องมือชิ้นหนึ่งของ &lt;strong&gt;Google Chrome DevTools&lt;/strong&gt; กันไปแล้ว คือตัว &lt;strong&gt;Code Inspector&lt;/strong&gt; ที่เอาไว้ใช้สำหรับตรวจสอบโครงสร้างของหน้าเว็บได้ เอาไว้ให้ developer ได้ตรวจสอบ ว่าโค้ดที่เขียนสามารถ render ออกมาเป็น HTML ได้ถูกต้องหรือเปล่า&lt;/p&gt;

&lt;p&gt;ตัว &lt;strong&gt;Network Inspector&lt;/strong&gt; ที่จะแนะนำให้รู้จักนี้ก็มีเอาไว้ด้วยเหตุผลคล้าย ๆ กันครับ ก็คือเพื่อให้ developer สามารถตรวจสอบได้ ว่าหน้าเว็บที่สร้างเนี่ย ได้ทำการรับส่งข้อมูลได้ถูกต้องตามที่ออกแบบเอาไว้หรือไม่ครับ เช่น เมื่อกดปุ่ม &lt;em&gt;submit&lt;/em&gt; แล้ว ข้อมูลใน form ที่กรอกเอาไว้ ถูกส่งไปที่ server ได้ถูก URL ไหม และข้อมูลอยู่ใน format ที่ถูกต้องหรือเปล่านั่นเองครับ&lt;/p&gt;

&lt;p&gt;และเช่นเดียวกับ Code Inspector คือเราสามารถใช้เครื่องมือตัวนี้ ให้เป็นประโยชน์ในการทำ scraping ได้ครับ!&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;เว็บข่าวไทยรัฐ&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เว็บที่จะใช้เป็นตัวอย่างครั้งนี้คือเว็บข่าวของไทยรัฐ หน้านี้นะครับ &lt;a href="https://www.thairath.co.th/news/politic"&gt;thairath.co.th/news/politic&lt;/a&gt; ครับ โดยให้เราเลื่อนหน้าจอลงมาที่ส่วนที่เขียนว่า &lt;em&gt;ข่าวอื่นๆ&lt;/em&gt; ตามในภาพด้านล่าง จะเห็นว่าหน้าเว็บนี้เลือกที่จะแสดงแค่ 12 ข่าวล่าสุดเท่านั้น โดยจะมีปุ่ม &lt;em&gt;ดูเพิ่ม&lt;/em&gt; ที่ด้านล่างให้กด เพื่อให้เว็บทำการโหลดข่าวมาแสดงเพิ่มครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GWH7fH9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w6fqjdyikk8xauudsuop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GWH7fH9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w6fqjdyikk8xauudsuop.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในกรณีแบบนี้ ถ้าเรายิง requests ไปที่ URL &lt;code&gt;https://www.thairath.co.th/news/politic&lt;/code&gt; โดยตรง เราจะไม่ได้ข้อมูลตรงนี้ีมาครับ เนื่องจากข้อมูลมันจะยังไม่ถูกโหลดเข้ามาในตอนแรก เพราะฉะนั้นเราจะต้อง &lt;strong&gt;ทำการเลียนแบบการ request ของปุ่ม "ดูเพิ่ม" (ขั้นตอนที่ 4 ใน diagram ตรงลูกศรสีแดง)&lt;/strong&gt; ดูว่ามันโหลดข้อมูลใหม่มาเพิ่มได้อย่างไร แล้วเราก็ทำตามนั่นเองครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UJkqDid8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p3jqmm7nfy4tvgsjcvbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UJkqDid8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p3jqmm7nfy4tvgsjcvbk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;diagram แสดงการรับส่งข้อมูล ของเว็บ Thairath กับฝั่ง server&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1. เปิด Network Inspector&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ขั้นแรกให้เราไปที่ &lt;a href="https://www.thairath.co.th/news/politic"&gt;หน้าเว็บ&lt;/a&gt; ก่อนนะครับ แล้วกดปุ่ม &lt;code&gt;F12&lt;/code&gt; เพื่อเปิด &lt;strong&gt;Chrome DevTools&lt;/strong&gt; ขึ้นมา จะมีหน้าต่างด้านขวาคล้าย ๆ ในภาพขึ้นมาครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wNeVg7Yz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/beuqvlx4esw7g8e2ow3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wNeVg7Yz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/beuqvlx4esw7g8e2ow3b.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จากนั้นให้เราเลือกเครื่อง &lt;strong&gt;Network Inspector&lt;/strong&gt; โดยคลิกที่คำว่า &lt;strong&gt;Network&lt;/strong&gt; จากแทบด้านบน (ของบางท่านอาจจะถูกย่อเอาไว้อยู่ ให้ลองเปิดหาดูจากตรงลูกศร &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;) เมื่อเปิดมาแล้วจะเห็นเป็นหน้าว่าง ๆ แบบในรูปครับ นี่คือ Network Inspector พร้อมใช้งานแล้วครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sngniWmC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rvn60ie7yisvl6c4p3qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sngniWmC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rvn60ie7yisvl6c4p3qg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;2. ลองโหลดข้อมูล&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ตอนนี้เมื่อเปิดหน้า Inspector ขึ้นมาแล้ว ถ้าหากมีการโหลดข้อมูลอะไรก็ตามเกิดขึ้น รายละเอียดของการโหลดข้อมูลจะแสดงขึ้นมาทันทีครับ&lt;/p&gt;

&lt;p&gt;ให้ลองกดปุ่ม "ดูเพิ่ม" เพื่อโหลดข่าวมาแสดงเพิ่มเติม ก็จะเห็นข้อมูลหลายอย่างถูกโหลดขึ้นมาพร้อมกันดังภาพ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qJFzo86M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x8fz34m3vm9uyarkabp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qJFzo86M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x8fz34m3vm9uyarkabp9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;แถบสีด้านบนจะบอกว่าข้อมูลแต่ละชิ้น ถูกโหลดมาตอนไหน และใช้เวลาในการโหลดนานเท่าไหร่ ส่วนตารางรายการด้านล่างจะบอกว่ามันโหลดอะไรมาบ้างครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;3. ดูข้อมูลของการ request&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;จากในรายการจะเห็นว่าตั้งแต่ข้อมูลบรรทัดที่ 2 เป็นต้นไป จะเป็นชื่อมั่ว ๆ ทั้งนั้น ซึ่งเป็นรูปภาพของแต่ละข่าวครับ สังเกตุได้จากที่คอลัมน์ &lt;code&gt;Type&lt;/code&gt; จะเขียนว่าเป็นไฟล์ &lt;code&gt;jpeg&lt;/code&gt; หรือลอง double คลิกเข้าไปก็จะสามารถดูภาพแต่ละภาพได้ครับ&lt;/p&gt;

&lt;p&gt;แต่เราไม่ได้สนใจรูปภาพ สิ่งที่เราหาอยู่คือตัวข้อมูลที่โหลดขึ้นมา ซึ่งโชคดีที่เว็บนี้้ไม่ได้มีการโหลดข้อมูลซับซ้อนมาก เราจึงสามารถหาสิ่งที่เราต้องการได้จากชื่อ ลองเดาดู และลองกดเข้าไปดูทีละอันได้ครับ&lt;/p&gt;

&lt;p&gt;ให้ลองคลิกเข้าไปที่ข้อมูลบรรทัดแรก จะเห็นว่ามันจะแสดงรายละเอียดที่สำคัญที่ใช้ request ไปทั้งหมดออกมา&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DsmjFPNE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x6rfm75hp4ryid9rb0l0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DsmjFPNE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/x6rfm75hp4ryid9rb0l0.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ซึ่งข้อมูลที่แสดงออกมานี้ เพียงพอให้เราสามารถนำไป request เลียนแบบเองใน python ได้ครับ ตั้งแต่ &lt;strong&gt;URL คือปลายทางที่เราส่งข้อมูลไป, method คือวิธีการส่ง ซึ่งสำหรับกรณีนี้คือ GET request, headers ที่ใช้ request&lt;/strong&gt; หรือถ้าบางที method ที่ใช้เป็น POST request ในส่วนท้ายสุดของหน้าก็จะแสดง Form Data พ่วงมาให้ด้วยครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7X28lbBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w7ly7zejygyqs0zu695h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7X28lbBj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w7ly7zejygyqs0zu695h.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ตัวอย่าง Form Data ที่จะถูกแสดงออกมาสำหรับ POST request (ไม่เกี่ยวกับเว็บนี้นะครับ เอามาให้ดูตัวอย่างเฉย ๆ)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ก่อนอื่นเลยนะครับ ให้เราเช็คให้แน่ใจก่อนว่าอันนี้เป็น URL ที่เราต้องการจริง ๆ คือมันจะให้ข้อมูลที่เราต้องการจริง ๆ ก่อน โดยการกดไปที่แทบ &lt;em&gt;Preview&lt;/em&gt; ครับ เพื่อดูว่ามันให้ข้อมูลอะไรกลับมา&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1An-0lHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a6ijj9mou69zgg78ahtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1An-0lHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a6ijj9mou69zgg78ahtr.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ก็จะเห็นว่าข้อมูลที่ถูกส่งกลับมา เป็น &lt;strong&gt;JSON&lt;/strong&gt; ครับ โดยมี items เป็น array ของรายละเอียดแต่ละข่าวที่ถูกโหลดขึ้นมาจริง ๆ เพราะฉะนั้น อันนี้ก็คือ URL ของ API ที่เราต้องการจริง ๆ นั่นเองครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;4. เลียนแบบ request&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ต่อไปเราก็เอารายละเอียดของ request ตัวนั้นมาลอกเลียนแบบใน Python นะครับ ก็จะได้เป็นตามโค้ดด้านล่าง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.thairath.co.th/loadmore&amp;amp;section=/news/politic&amp;amp;ts=1597188900&amp;amp;limit=8'&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# เนื่องจากผลลัพธ์ที่ออกมาเป็น JSON เราสามารถใช้ .json() เพื่อแปลงให้เป็น dict ใน Python ได้เลย
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;จะได้ข้อมูล data ออกมาเป็น dict หน้าตาประมาณนี้นะครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yoBQmBPr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cxrfxgpdh0ikbp1aj5du.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yoBQmBPr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cxrfxgpdh0ikbp1aj5du.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
...&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D8jzuxlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aa5wiwx2dwykbleijcz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D8jzuxlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aa5wiwx2dwykbleijcz7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;5. Pagination?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เพิ่มเติมอีกเล็กน้อยครับ จากข้อก่อนเราได้ข้อมูลมาเสร็จแล้วก็จริงครับ แต่มันได้มาแค่หน้าเดียว ทีนี้ถ้าเราอยากจะเก็บให้หมดทุกหน้าเลย ก็ต้องใช้ Inspector ไปก๊อปมาทุกหน้าเรื่อย ๆ หรือ??? คำตอบคือไม่ใช่ครับ&lt;/p&gt;

&lt;p&gt;เนื่องจากว่าเราได้รู้ตัว API ของเว็บที่ใช้เป็นตัวโหลดข้อมูลแต่ละหน้ามาเรียบร้อย &lt;strong&gt;วิธีการโหลดหน้าอื่น ๆ เราก็แค่เปลี่ยนข้อมูลที่ส่งให้ถูกต้องเท่านั้นครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ซึ่งวิธีการเปลี่ยนเนี่ย ก็ขึ้นอยู่กับเว็บเป้าหมายกำหนดเลยครับ บางเว็บใช้เลขหน้าแบบง่าย ๆ 1, 2, 3, 4, ... บางเว็บอาจใช้เลข ID ของ content ที่โหลดไปล่าสุดส่งต่อไปให้ เป็นต้น ลักษณะพวกนี้เรียกว่า &lt;strong&gt;Pagination&lt;/strong&gt; ครับ&lt;/p&gt;

&lt;p&gt;อย่างในกรณีนี้ ถ้าเราดู URL ที่เราใช้ดี ๆ ก็จะเห็นว่ามันส่งข้อมูลไป 2 อย่าง ได้แก่ &lt;code&gt;ts=1597188900&lt;/code&gt; และ &lt;code&gt;limit=8&lt;/code&gt; ตัว limit=8 เนี่ยเราเดาได้ใช่ไหมครับ ว่ามันหมายถึงจำนวนของข่าวที่ขอไป คือให้โหลดมาแค่ 8 ข่าวต่อครั้งก็พอ แล้ว ts ล่ะ??&lt;/p&gt;

&lt;p&gt;คำใบ้อยู่ที่ข้อมูลที่ส่งกลับมาครับ จะเห็นว่าในแต่ละครั้งที่ request ข้อมูลที่ส่งกลับมาจะมีค่า minTs อยู่ด้วย ซึ่งจำนวนหลักและตัวเลขมันก็ดูคล้าย ๆ กับ ts ที่เราส่งไปนะครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D8jzuxlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aa5wiwx2dwykbleijcz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D8jzuxlA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aa5wiwx2dwykbleijcz7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้าเราลองใช้ minTs ที่ส่งมา เอาไปยิงต่อดูตามโค้ดด้านล่างก็จะเห็นว่าจะได้ข้อมูลของหน้าถัดไปมาพอดีครับ เพราะฉะนั้นต่อให้เราไม่รู้ว่า ts คืออะไร เราก็จะพอเดาได้ว่า minTs ที่ส่งมาเนี่ย เอาไว้สำหรับให้เราสามารถโหลดข้อมูลของหน้าต่อ ๆ ไปได้นั่นเองครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.thairath.co.th/loadmore&amp;amp;section=/news/politic&amp;amp;ts=1597149600&amp;amp;limit=8'&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;note: จริง ๆ แล้ว ts ก็คือ timestamp นั่นเองครับ เห็นตัวย่อ และเห็นตัวแรกแล้ว หลาย ๆ ท่านน่าจะเดาได้ตั้งแต่แรกใช่ไหมครับ 55&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;เพราะฉะนั้นเราสามารถที่จะ automate การดึงข่าวมาหลาย ๆ หน้าดังโค้ดด้านล่าง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'https://www.thairath.co.th/loadmore&amp;amp;section=/news/politic&amp;amp;limit=8'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;f'https://www.thairath.co.th/loadmore&amp;amp;section=/news/politic&amp;amp;ts=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;limit=8'&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                   &lt;span class="c1"&gt;# ใช้ url เริ่มต้น คือไม่ต้องส่งค่า ts ไป
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;               &lt;span class="c1"&gt;# วนไป 10 หน้า
&lt;/span&gt;    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# โหลดข้อมูล
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            &lt;span class="c1"&gt;# แปลงเป็น dict
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;       &lt;span class="c1"&gt;# ไล่ print title ของแต่ละข่าว
&lt;/span&gt;        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'minTs'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# สร้าง url ใหม่ โดยส่ง ts ให้มีค่าเท่ากับ minTs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pTYjb3tp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rt0ubummrv73d5y2ja54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pTYjb3tp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rt0ubummrv73d5y2ja54.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;จบไปแล้วนะครับ 5 parts สำหรับพื้นฐานทั้งหมดในการทำ Web Scraping ด้วย Python ถ้าเข้าใจทั้งหมดก็น่าจะสามารถดึงข้อมูลจากเว็บส่วนใหญ่ได้เยอะแล้วครับ แล้วก็อย่าลืมติดตามบทความต่อ ๆ ไปเกี่ยวกับการ scraping ด้วยนะครับ เดี๋ยวเรามาลองวิชากับเว็บจริง ๆ กัน&lt;/p&gt;

&lt;p&gt;และก็ขอย้ำอีกครั้ง &amp;lt;เหมือนที่ย้ำอยู่ตลอดตั้งแต่ part แรก&amp;gt; ก็คือบทความนี้ทำขึ้นเพื่อการศึกษานะครับ ในการนำไปใช้จริง ควรคำนึงถึงความเหมาะสม และไม่ทำให้ผู้อื่นเดือดร้อนนะครับ&lt;/p&gt;

&lt;p&gt;ถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;part อื่น ๆ ใน series&lt;/strong&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part 1: การดูดข้อมูลเบื้องต้น ด้วย Python&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2: Chrome's Code Inspector&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h"&gt;part 3: เทคนิคการ extract ข้อมูลด้วย XPath&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4"&gt;part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf"&gt;part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>datascience</category>
      <category>html</category>
    </item>
    <item>
      <title>Google Cloud Run - deploy docker แบบง่าย ๆ ราคาถูก แต่รับ traffic คนได้ทั้งประเทศ!</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sat, 27 Jun 2020 14:42:43 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/google-cloud-run-deploy-docker-traffic-5552</link>
      <guid>https://dev.to/copypasteengineer/google-cloud-run-deploy-docker-traffic-5552</guid>
      <description>&lt;p&gt;อย่างที่ทราบกันว่า &lt;strong&gt;Google Cloud&lt;/strong&gt; นี่มี services ให้เล่นกันเยอะมาก สำหรับทั้งงานในด้านเว็บ ทำระบบ data science และอื่น ๆ วันนี้จะมาแนะนำ service ตัวหนึ่งครับ เอาไว้สำหรับ deploy docker container ของ web app เรา ซึ่งเหมาะสำหรับทั้ง dev มือใหม่ที่อยากจะลอง deploy web app ดูเล่น ๆ ยันมือโปรที่จะเอาไปใช้งานบน production จริง เนื่องจากการใช้งานค่อนข้างง่าย แต่สามารถ scale ให้รองรับงานใหญ่ ๆ ระดับทั้งประเทศได้สบาย และที่สำคัญยังราคาไม่แพงอีกด้วย ก็คือตัว &lt;strong&gt;Google Cloud Run&lt;/strong&gt; นี่เองครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KiMbtyiF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qgeftym98h5pzyevxciz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KiMbtyiF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qgeftym98h5pzyevxciz.png" alt="google-serverless-devops.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ตัวอย่าง products ต่าง ๆ ของ Google ที่เกี่ยวข้อง สำหรับการ deploy apps แบบ serverless จาก &lt;a href="https://medium.com/google-cloud/journey-to-serverless-on-google-cloud-platform-67b8d392ffa2"&gt;Journey to Serverless on Google Cloud Platform&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Google Cloud Run&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Google Cloud Run เป็น serverless service ตัวหนึ่ง ในกลุ่มของ &lt;strong&gt;Compute Platform&lt;/strong&gt; คล้าย ๆ กับพวก &lt;em&gt;Google App Engine&lt;/em&gt; หรือ &lt;em&gt;Google Kubernetes Engine&lt;/em&gt; ที่หลาย ๆ ท่านอาจจะคุ้นเคยกันดี คือเป็น product ที่ให้เราใช้เป็นส่วนของ compute เพื่อ deploy application ของเราเช่นเดียวกัน โดย Cloud Run นี้เป็น service ใหม่ที่ Google เพิ่งออกมาไม่นานนี้ครับ&lt;/p&gt;

&lt;p&gt;ซึ่งจะมีส่วนคล้ายกับตัว Kubernetes ตรงที่จะ &lt;strong&gt;deploy โดยใช้ Docker image ทำให้สามารถ scale เพื่อเพิ่มจำนวนของ container ให้รับ traffic จำนวนมากได้เหมือนกัน&lt;/strong&gt; แต่ต่างกันตรงที่ Cloud Run App หนึ่ง ๆ จะสามารถ deploy Docker image ได้แค่ image เดียวเท่านั้น ในขณะที่ Kubernetes สามารถ deploy หลาย ๆ image เพื่อมาทำงานร่วมกันได้ในทีเดียวครับ ทำให้ในบาง app ที่มีหลาย ๆ image ทำงานร่วมกัน เราก็ต้อง deploy ลง Cloud Run ทีละ image&lt;/p&gt;

&lt;p&gt;แต่สิ่งที่ Cloud Run จะได้เปรียบกว่า Kubernetes ก็คือ Cloud Run นั้น &lt;strong&gt;มีความซับซ้อนน้อยกว่า&lt;/strong&gt; และก็ &lt;strong&gt;ไม่ต้องเขียน config ให้ยุ่งยาก&lt;/strong&gt; แบบ Kubernetes ครับ ซึ่งไม่ว่าจะเรื่องของการทำ auto-scaling หรือ load-balancing นี่ &lt;strong&gt;Google Cloud Run จัดการให้เองทั้งหมดครับ&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;จุดเด่น&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; คือ Google Cloud Run สามารถ &lt;strong&gt;deploy ได้ง่ายมาก&lt;/strong&gt; ไม่ต้อง config เพียงแค่เรา push docker image ขึ้นไปใน project เรา จากนั้นก็สั่ง deploy คำสั่งเดียว ก็จบครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. ทำ auto-scaling ให้อัตโนมัติ&lt;/strong&gt; สามารถเพิ่มจำนวน instances จำนวนมากได้ภายในเวลาอันสั้น&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oztP03YU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vaxpci3e35ogck72z4rs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oztP03YU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vaxpci3e35ogck72z4rs.png" alt="auto-scaling-test.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพการทดสอบ auto-scaling ของ Cloud Run จาก &lt;a href="https://youtu.be/xVuuvZkYiNM?t=1638"&gt;คลิปงาน Cloud Next '19&lt;/a&gt; โดยใช้วิธีการจำลอง user ขึ้นมา แล้วให้ยิง request ไปที่ Cloud Run App เดียวกัน ไปเรื่อย ๆ โดยจำนวนของ user จะค่อย ๆ เพิ่มขึ้นเรื่อย ๆ จนไปถึง 15,000 คน!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;จากกราฟ แกนตั้งคือเวลาที่ใช้ในการ request (latency) ส่วนแกนนอนคือเวลาที่ผ่านไป จะเห็นว่าในช่วงแรก latency ค่อนข้างสูง เนื่องจากว่าอยู่ ๆ user ก็เพิ่มจำนวนขึ้นกระทันหัน และยังเพิ่มขึ้นเรื่อย ๆ แต่เมื่อผ่านไปสักพัก ที่ประมาณวินาทีที่ 25 latency ก็ drop ลง และค่อนข้างจะคงที่หลังจากนั้น นั่นคือ &lt;strong&gt;Google Cloud Run สามารถ handle burst ของ load หลักหมื่นได้ภายในไม่กี่วินาที!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. เป็น Serverless Service&lt;/strong&gt; หมายความว่า Google จะ manage ตัว infrastructure ให้หมด ทำให้เราไม่ต้องสนใจว่าจะต้อง setup เครื่องยังไง หรือจะเอาไปลงเครื่องไหน ให้เราคิดเสมือนว่าไม่มี server อยู่นั่นเองครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; เนื่องจากว่าเป็น serverless การคิดราคาจึงไม่ได้คิดจากเวลาที่เครื่อง server ทำงาน แบบ service อื่น ๆ (เช่น App Engine) &lt;strong&gt;แต่จะคิดเงินเฉพาะจากเวลาที่ใช้งานจริงเท่านั้น&lt;/strong&gt; หมายความว่าถ้าวันไหนไม่มีคนเข้าเว็บเรา เราก็ไม่เสียเงินเลยครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JQet5PxT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s5sda0c9lgcpdftfdy1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JQet5PxT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s5sda0c9lgcpdftfdy1h.png" alt="billable-time.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพของช่วงเวลาที่จะคิดเงิน คือจะคิดเงินช่วงที่มีคนยิง request เข้ามาและประมวลผลเท่านั้น (สีฟ้า) ช่วงที่ไม่มี request ก็ไม่คิดเงิน (สีเทา)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. รองรับโค้ดทุกภาษา&lt;/strong&gt; เนื่องจากว่าใช้ docker image ในการ deploy ครับ ดังนั้นจะโค้ดด้วยภาษาอะไร ถ้าเอาใส่ docker ได้ ก็จบ ซึ่งต่างกับตัว App Engine ที่ Support แค่บางภาษาเท่านั้น&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;วิธี deploy&lt;/strong&gt;
&lt;/h1&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;สิ่งที่ต้องเตรียม&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Google Cloud project&lt;/strong&gt; ซึ่งเปิดอนุญาตการ Billing ไว้แล้ว; ถ้ายังไม่เปิด สามารถทำตามขั้นตอนใน &lt;a href="https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_an_existing_project"&gt;link นี้&lt;/a&gt; ได้ครับ แค่คลิก ๆ ไม่นาน&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Google Cloud SDK&lt;/strong&gt; เพื่อความสะดวกในการใช้งาน products ทุก product ของ Google ควรจะต้องติดตั้ง program นี้ไว้ครับ เมื่อติดตั้งแล้ว เราจะสามารถควบคุม services ต่าง ๆ ของ Google ได้ผ่านคำสั่งใน command line สามารถติดตั้งได้ตาม &lt;a href="https://cloud.google.com/sdk/docs"&gt;link นี้&lt;/a&gt; เลยครับ&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;การ deploy&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. submit docker image&lt;/strong&gt; ให้เข้าไปที่ directory ที่มี &lt;strong&gt;Dockerfile&lt;/strong&gt; ที่เราเตรียมเอาไว้อยู่ครับ เสร็จแล้ว run คำสั่งนี้ เพื่อทำการ submit โค้ดต่าง ๆ ขึ้นไป และ build Container Image บน Google Cloud ด้วย service ที่ชื่อ &lt;strong&gt;Cloud Build&lt;/strong&gt; ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud builds submit &lt;span class="nt"&gt;--tag&lt;/span&gt; asia.gcr.io/PROJECT_ID/IMAGE_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ให้เปลี่ยน &lt;code&gt;PROJECT_ID&lt;/code&gt; เป็น &lt;em&gt;ชื่อ Google Cloud project ของเรา&lt;/em&gt; และเปลี่ยน &lt;br&gt;
&lt;code&gt;IMAGE_NAME&lt;/code&gt; เป็น &lt;em&gt;ชื่อ app ของเรา&lt;/em&gt; นะครับ&lt;/p&gt;

&lt;p&gt;ถ้าใช้ Cloud Build ครั้งแรก มันจะถามว่าให้ enable API &lt;code&gt;cloudbuild.googleapis.com&lt;/code&gt; ของ Cloud Build ไหม ก็ให้กด &lt;code&gt;y&lt;/code&gt; แล้ว ENTER ไป มันก็จะทำงานต่อ&lt;/p&gt;

&lt;p&gt;Cloud Build จะทำการ build ตาม Dockerfile ที่เราเขียนไว้ แล้ว push image ไปที่ registry ใน project ของเรา (ที่ &lt;strong&gt;asia.gcr.io/PROJECT_ID/IMAGE_NAME&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: ผมใช้ hostname เป็น &lt;strong&gt;asia.gcr.io&lt;/strong&gt; หมายความว่า เราจะเก็บ image ของเราไว้ใน registry ที่อยู่ใน asia ครับ ซึ่งเราสามารถเปลี่ยน location ไปที่อื่น ๆ ได้ เช่น us.gcr.io, หรือ eu.gcr.io ตาม location ที่เราใช้งานครับ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. deploy image ลง Cloud Run&lt;/strong&gt; ตอนนี้เรา build app ของเราเป็น container image ไปเก็บเอาไว้แล้วนะครับ ทีนี้ก็แค่บอก Google Cloud Run ว่าเอา image อันนี้ไปใช้ที เท่านั้นครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud run deploy IMAGE_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--image&lt;/span&gt; asia.gcr.io/PROJECT_ID/IMAGE_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--platform&lt;/span&gt; managed &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt; asia-east1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--port&lt;/span&gt; PORT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;อธิบายคำสั่งทีละบรรทัด&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; ให้ deploy โดยใช้ชื่อ &lt;em&gt;&lt;strong&gt;IMAGE_NAME&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; ด้วย image จาก &lt;em&gt;&lt;strong&gt;asia.gcr.io/PROJECT_ID/IMAGE_NAME&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; ด้วย mode &lt;em&gt;&lt;strong&gt;managed&lt;/strong&gt;&lt;/em&gt; คือให้ Google managed ให้อัตโนมัติ ซึ่งจะมี mode อื่น ๆ ที่ไปใช้ cluster ที่เรามีมารันแทน แต่ง่ายสุด ก็ใช้ managed ไปก่อนครับ&lt;br&gt;&lt;br&gt;
&lt;strong&gt;4.&lt;/strong&gt; ไปที่ region &lt;em&gt;&lt;strong&gt;asia-east1&lt;/strong&gt;&lt;/em&gt; จะอยู่ที่ Taiwan ซึ่งเป็นที่ location หนึ่งที่ไม่ไกลจากไทยมาก ที่สามารถใช้ Google Cloud Run ได้ (location ที่ใกล้ไทยที่สุดคือ &lt;em&gt;&lt;strong&gt;asia-southeast2&lt;/strong&gt;&lt;/em&gt; ที่ Jakarta แต่ ณ ตอนนี้ยังไม่สามารถใช้ Cloud Run ได้ครับ)&lt;br&gt;
&lt;strong&gt;5.&lt;/strong&gt; allow-unauthenticated คือ อนุญาตคนอื่นสามารถใช้ app นี้ได้ แต่ถ้าอยากทำเพื่อใช้เฉพาะ dev ที่ได้รับอนุญาตเท่านั้น ก็สามารถเปลี่ยนเป็น &lt;code&gt;--no-allow-unauthenticated&lt;/code&gt; แทนได้ครับ&lt;br&gt;&lt;br&gt;
&lt;strong&gt;6.&lt;/strong&gt; application เราจะรันจาก port &lt;em&gt;&lt;strong&gt;PORT&lt;/strong&gt;&lt;/em&gt; แทนค่า PORT เป็น port ที่เราจะใช้งานนะครับ&lt;/p&gt;

&lt;p&gt;อันนี้ก็เหมือนกัน คือถ้าเราไม่เคยใช้ Cloud Run มาก่อน มันก็จะถามว่าให้ enable API &lt;code&gt;run.googleapis.com&lt;/code&gt; ของ Cloud Run ไหม ก็กด &lt;code&gt;y&amp;lt;ENTER&amp;gt;&lt;/code&gt; ไป&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: เพิ่มเติมนิดนึง คือมี options อื่น ๆ ที่ปรับได้อีกนะครับเช่นจำนวน &lt;strong&gt;CPU ว่าจะเอากี่ตัว&lt;/strong&gt; หรือ ขนาดของ &lt;strong&gt;RAM ว่าจะใช้กี่ MB&lt;/strong&gt; สามารถดูเพิ่มเติมได้ใน &lt;a href="https://cloud.google.com/sdk/gcloud/reference/run/deploy"&gt;link นี้&lt;/a&gt; เลยครับ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ถ้า deploy สำเร็จ ก็จะ message ประมาณนี้ขึ้นมา&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;Done.                                                                                                                                                                                                             
Service &lt;span class="o"&gt;[&lt;/span&gt;myapp] revision &lt;span class="o"&gt;[&lt;/span&gt;myapp-00003-gej] has been deployed and is serving 100 percent of traffic at https://myapp-cee6n4roda-de.a.run.app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เราก็เอา link ที่มันให้มา มาเปิดดูใน browser ได้เลยครับ ก็จะเห็น app ของที่เราเขียนไว้ อย่างของผม ลิ้งคือ &lt;code&gt;https://myapp-cee6n4roda-de.a.run.app&lt;/code&gt; พอไปเปิดใน browser ก็จะเป็น Django app ขึ้นมาแบบนี้&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--upbO-rXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5s3mu51oycvfanh42kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--upbO-rXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5s3mu51oycvfanh42kf.png" alt="deployed-app.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ก็เป็นอันเสร็จสิ้นครับ แค่ 2 ขั้นตอน สามารถแชร์ลิ้งให้เพื่อนไปเล่น หรือนำไปใช้งานจริงได้เลย&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;หน้า UI สำหรับจัดการต่าง ๆ&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;เราได้ลอง deploy โดยใช้ command line ไปแล้วนะครับ ซึ่งโดยปกติเราก็คงจะใช้วิธีนั้นการทำงานจริงใช่ไหมครับ ทั้งเวลาทำ ci/cd เขียน pipeline ให้ deploy อัตโนมัติ ก็จะใช้คำสั่งตามด้านบนในการทำ&lt;/p&gt;

&lt;p&gt;แต่ Google Cloud ก็ยังมีหน้า GUI ให้ใช้ผ่าน browser เพื่ออำนวยความสะดวกทุกคน ให้สามารถ deploy และตั้งค่าต่าง ๆ ได้แบบง่าย ๆ อีกด้วย สามารถเข้าผ่าน link นี้ได้เลยครับ &lt;a href="https://console.cloud.google.com/run"&gt;https://console.cloud.google.com/run&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JrPbGGU9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8ua6klfgd3a6az5r0lm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JrPbGGU9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8ua6klfgd3a6az5r0lm8.png" alt="cloud-run-ui-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จะเห็นว่ามีปุ่มให้ &lt;em&gt;สร้างบริการ&lt;/em&gt; (สร้าง service) ก็คือทำการ deploy ผ่าน UI นี้ได้ และสามารถที่ดู แก้ไข หรือลบ service ที่เราสร้างเอาไว้แล้วได้ง่าย ๆ ผ่านหน้านี้เลยครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;การเก็บเงิน&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;อย่างที่ทราบคือ Cloud Run นั้นเป็น serverless service ดังนั้นการเก็บเงิน ก็จะเก็บตามปริมาณที่เราใช้ไปเท่านั้น โดยจะมีด้วยกัน 4 ส่วนครับ&lt;br&gt;
&lt;strong&gt;1. CPU&lt;/strong&gt; ก็คือ ยิ่งเราใช้ CPU แรง และทำการคำนวณนานใน request แต่ละครั้ง ราคาก็จะแพงครับ&lt;/p&gt;

&lt;p&gt;โดย Google จะใจดี &lt;strong&gt;ให้ฟรีเดือนละ 180,000 vCPU-seconds&lt;/strong&gt;&lt;br&gt;
หลังจากนั้นก็จะคิดราคา &lt;strong&gt;$0.00002400 / vCPU-seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: หน่วย &lt;strong&gt;vCPU-seconds&lt;/strong&gt; หมายถึงจำนวน CPU ที่ app เราใช้ คูณกับเวลาที่ใช้รันทั้งหมดครับ อย่างที่บอกด้านบนว่าจำนวน CPU และ RAM ที่เราจะ allocate ไว้นี่ สามารถปรับได้&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ตัวอย่างเช่น ถ้าเราเลือก &lt;strong&gt;2 CPU&lt;/strong&gt; แล้วในเดือนหนึ่งเรารันไปเป็นเวลา &lt;strong&gt;100,000 วินาที&lt;/strong&gt;&lt;br&gt;
ก็จะคิดเป็น &lt;em&gt;&lt;strong&gt;2 vCPU x 100,000 seconds = 200,000 vCPU-seconds&lt;/strong&gt;&lt;/em&gt; ครับ&lt;br&gt;
ซึ่ง &lt;strong&gt;Google ให้ฟรีมา 180,000 vCPU-seconds&lt;/strong&gt;&lt;br&gt;
เพราะฉะนั้นในเดือนนั้นเราก็ต้องจ่ายที่เหลือ &lt;em&gt;&lt;strong&gt;20,000 vCPU-seconds x $0.00002400 = $0.48&lt;/strong&gt;&lt;/em&gt; เท่านั้นครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Memory&lt;/strong&gt; อันนี้ก็คิดคล้าย ๆ กับ CPU เลยครับ คือนับเป็นหน่วย &lt;strong&gt;GiB-seconds&lt;/strong&gt; ก็คือเอา GB ของ RAM ที่เราจองไว้ มาคูณเวลาที่ใช้นั่นเอง&lt;/p&gt;

&lt;p&gt;โดย Google จะให้ฟรีเดือนละ &lt;strong&gt;360,000 GiB-seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;แล้วหลังจากนั้น จะคิดราคา &lt;strong&gt;$0.00000250 / GiB-second&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. จำนวนครั้งที่ Requests&lt;/strong&gt; ฟังดูงกหน่อย ๆ นะครับ ที่คิดเวลาที่รัน แล้วยังมาคิดจำนวนครั้งอีก 555&lt;/p&gt;

&lt;p&gt;แต่ Google ก็ให้ &lt;strong&gt;ฟรีตั้ง 2 ล้านครั้งต่อเดือน&lt;/strong&gt; ครับ&lt;/p&gt;

&lt;p&gt;แล้วหลังจากนั้น ก็แค่ &lt;strong&gt;$0.40 / หนึ่งล้านครั้ง&lt;/strong&gt; เท่านั้น&lt;/p&gt;

&lt;p&gt;ทั้ง 3 ข้อด้านบนสามารถสรุปได้ตามตารางนี้ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jAm6WuAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2yhh8v9326r9xzpbekwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jAm6WuAz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2yhh8v9326r9xzpbekwf.png" alt="pricing-table.png"&gt;&lt;/a&gt;&lt;br&gt;
*rate นี้ไม่รวม australia และ northamerica นะครับ อ่านเพิ่มเติม ได้ที่ &lt;a href="https://cloud.google.com/run/pricing#tables"&gt;link นี้&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Network&lt;/strong&gt; ส่วน network นี่จะคิดเงินแค่ขาออก คือจาก application ออกไปข้างนอกเท่านั้น แต่จะค่อนข้างซับซ้อน เพราะขึ้นกับ geo location ของทั้ง source และ destination ตามภาพครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--38nkdMsK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/of9hx85t09pjil75e70l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--38nkdMsK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/of9hx85t09pjil75e70l.png" alt="pricing-table-network.png"&gt;&lt;/a&gt;&lt;br&gt;
อ่านเพิ่มเติมได้ที่ &lt;a href="https://cloud.google.com/network-tiers/pricing#premium-pricing"&gt;link นี้&lt;/a&gt; ครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;คิดว่าผู้อ่านคงพอได้เห็นภาพคร่าว ๆ ของตัว Cloud Run นี้ไปแล้วนะครับ ทั้งในเรื่องการทำงานของ service นี้, auto-scaling, วิธีการ deploy, และการคิดราคา&lt;/p&gt;

&lt;p&gt;โดย Cloud Run นี่เหมาะกับงานหลายอย่างมากครับ ทั้งในการทำ microservice ที่ไม่ซับซ้อนมาก หรือพวก frontend หรือเป็น job เล็ก ๆ ที่ตั้งเวลาให้รัน ผ่าน &lt;a href="https://cloud.google.com/scheduler"&gt;Cloud Scheduler&lt;/a&gt; ก็ได้ครับ&lt;/p&gt;

&lt;p&gt;ซึ่งด้วยความที่มันสามารถ deploy ได้ง่าย ไม่ต้อง config มาก คิดว่าอาจจะเหมาะกับนักศึกษาที่ต้องการ deploy งาน หรือทำ prototype แบบไว ๆ ด้วยครับ&lt;/p&gt;

&lt;p&gt;แต่ข้อเสียก็มีเช่นมันบังคับให้ access ผ่าน HTTP เท่านั้น แล้วยังจำกัดเวลาในการรันแต่ละ request ไว้ที่ 15 นาทีเท่านั้น&lt;/p&gt;

&lt;p&gt;ในระบบที่ซับซ้อนหน่อย Kubernetes ก็อาจจะยังเป็นตัวเลือกที่ดี&lt;/p&gt;

&lt;p&gt;ถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;อ้างอิง&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://cloud.google.com/run"&gt;https://cloud.google.com/run&lt;/a&gt;&lt;br&gt;
&lt;a href="https://cloud.google.com/run/pricing"&gt;https://cloud.google.com/run/pricing&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=xVuuvZkYiNM"&gt;https://www.youtube.com/watch?v=xVuuvZkYiNM&lt;/a&gt;&lt;br&gt;
&lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/when-to-use-google-kubernetes-engine-vs-cloud-run-for-containers"&gt;https://cloud.google.com/blog/products/containers-kubernetes/when-to-use-google-kubernetes-engine-vs-cloud-run-for-containers&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>googlecloud</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Google BigQuery - explore ข้อมูล Pantip ดูแบบง่าย ๆ</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Fri, 12 Jun 2020 13:15:33 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/google-bigquery-explore-pantip-1me9</link>
      <guid>https://dev.to/copypasteengineer/google-bigquery-explore-pantip-1me9</guid>
      <description>&lt;p&gt;ต่อเนื่องจากบทความคราวก่อนเรื่อง &lt;a href="https://dev.to/copypasteengineer/google-cloud-scraping-project-real-time-google-cloud-deploy-5-51ic"&gt;ส่องกระทู้ชาวพันทิปแบบ real-time ด้วย Google Cloud ฟรี (deploy เสร็จสรรพใน 5 นาที!)&lt;/a&gt; เราได้ระบบที่เก็บข้อมูล Pantip ทุก ๆ 10 นาที ลงบน &lt;strong&gt;Google BigQuery&lt;/strong&gt; ซึ่งเป็น Data Warehouse บน Google Cloud Platform แล้วนะครับ ในบทความนี้จะขอแนะนำวิธีการ explore ข้อมูลที่เรามีบน BigQuery และทำ Dashboard แบบง่าย ๆ ทำเสร็จภายในไม่กี่นาที โดยใช้เครื่องมือที่&lt;u&gt;มีมาใน Google BigQuery อยู่แล้ว&lt;/u&gt; ไม่ต้อง install อะไรเพิ่มเติมทั้งสิ้นครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;เริ่มกันเลย&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ถ้าจะทำตามไปด้วย ให้ไปทำตามขั้นตอนใน &lt;a href="https://dev.to/copypasteengineer/google-cloud-scraping-project-real-time-google-cloud-deploy-5-51ic"&gt;บทความก่อนหน้า&lt;/a&gt; ก่อนนะครับ ไม่อย่างนั้นจะไม่มี data ใน BigQuery ทำไม่นานเลยครับมีโค้ดให้เสร็จสรรพ ไม่ต้อง install อะไรลงในเครื่องเลย&lt;/p&gt;

&lt;p&gt;พอมี data แล้วก็เริ่มจากไปที่ &lt;a href="https://console.cloud.google.com/bigquery"&gt;หน้า console ของ BigQuery&lt;/a&gt; ก็จะเป็นตามภาพด้านล่างนะครับ คือด้านซ้ายล่างจะเป็นส่วนที่ให้ explore Dataset ที่เรามี ส่วนกล่องข้อความตรงกลางเอาไว้ให้เราพิมพ์คำสั่ง SQL เพื่อทำการ query ข้อมูลจาก table ของเรา ออกมาแสดงได้ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d9m1gfrh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9daiskfl12z27tzrjc1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d9m1gfrh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9daiskfl12z27tzrjc1x.png" alt="bigquery-looks.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตัวนี้แหละครับก็คือเครื่องมือหลักที่เราจะใช้เพื่อ explore ข้อมูลกัน โดยการเขียน SQL&lt;/p&gt;

&lt;p&gt;เนื่องจาก BigQuery เป็น serverless data warehouse ที่ optimized มาเพื่อสำหรับการทำ analytics โดยเฉพาะครับ ทำให้เราสามารถ &lt;u&gt;process ข้อมูลขนาดใหญ่ได้อย่างรวดเร็ว&lt;/u&gt; (ข้อมูลเป็นระดับเป็นสิบ ๆ Gigabytes ก็อาจจะใช้เวลาประมวลผลไม่กี่วินาทีเท่านั้น) และยังราคาไม่แพงมาก ไม่ต้องซื้อ hardware มาตั้งและ maintain เอง Google จะทำการ &lt;u&gt;scale ให้อัตโนมัติครับ&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;ต่อไป จากทางด้านกล่องซ้ายล่าง กดเลือก Dataset และ Table ที่เรา scrape มา ก็จะเห็นรายละเอียดของ Table นั้น ๆ ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CmR47lIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/opfu8d78kq2931fcpbdq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CmR47lIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/opfu8d78kq2931fcpbdq.png" alt="table-detail-1.png"&gt;&lt;/a&gt;&lt;br&gt;
รูปแสดง schema ของ table ตรงนี้ผมแก้จากโค้ดในบทความก่อนเล็กน้อยครับ คือให้มันเก็บข้อมูลมาทุก tag ที่มี แล้วเก็บตัวแปร tags มาด้วย เป็น array type ของ tags ใน schema เลยเป็น &lt;code&gt;STRING&lt;/code&gt; &lt;code&gt;REPEATED&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ที่แถบด้านบนก็สามารถคลิกดูรายละเอียดของ Table และดูตัวอย่างข้อมูลได้ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n6kETPPi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ixz3wczl6w9d7mhqoyh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n6kETPPi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ixz3wczl6w9d7mhqoyh5.png" alt="table-detail-2.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vDJ7Zrpt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7dz2luyrfuj5xkjadilo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vDJ7Zrpt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7dz2luyrfuj5xkjadilo.png" alt="table-detail-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จะเห็นจากใน detail ว่าข้อมูลผมตอนนี้มีประมาณ 255,000 แถว&lt;br&gt;
ทีนี้ลองมา query เพื่อดู stats ของข้อมูลเพิ่มเติมกันนะครับ&lt;/p&gt;

&lt;p&gt;เริ่มจากลองดูช่วงของข้อมูลก่อน ว่ามีข้อมูลตั้งแต่วันไหนถึงวันไหน ก็คือหา min, max ของ field &lt;code&gt;created_time&lt;/code&gt; ธรรมดาครับ โดยการพิมพ์คำสั่ง SQL ลงในกล่องข้อความตรงกลาง แล้วกปุ่ม &lt;code&gt;เรียกใช้&lt;/code&gt; ผลลัพธ์ก็จะออกมาเป็นตารางด้านล่างตามภาพนะครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;min_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;max_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TgMNiOf3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gpespra7wl08opmgya4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TgMNiOf3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gpespra7wl08opmgya4w.png" alt="query-min-max-time.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ต่อไปผมอยากรู้ว่า ในช่วง 3 เดือนนี้เนี่ย tag ไหนมีจำนวนกระทู้มากน้อยอย่างไรบ้าง &lt;br&gt;
ขอไม่ลงรายละเอียด SQL มากนะครับ ไอเดียก็คือ field &lt;code&gt;tags&lt;/code&gt; เนี่ยมันเป็น array เราต้องใช้ &lt;code&gt;UNNEST()&lt;/code&gt; ในการ "กระจาย" มันออกมา จากนั้นก็ GROUP BY tag แต่ละ tag แล้ว count ออกมา แล้วก็ให้เรียงลำดับ ORDER ตาม count จากมากไปน้อยครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็จะได้ผลลัพธ์เป็น tags 10 อันดับแรก ที่มีจำนวนกระทู้มากที่สุดในสามเดือนนี้นะครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AePWlxjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pgf3ul7b0kva8x78k0yq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AePWlxjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pgf3ul7b0kva8x78k0yq.png" alt="query-tag-count.png"&gt;&lt;/a&gt;&lt;br&gt;
จากภาพ ด้านบนก็จะมีแสดงเวลาที่ใช้ในการคำนวณด้วยนะครับ จะเห็นว่าใช้เวลาไปแค่ 1 วินาทีนิด ๆ เท่านั้น&lt;/p&gt;

&lt;p&gt;เหมือนว่าส่วนใหญ่จะเป็นเรื่องความรักนะครับ ไม่ค่อยน่าแปลกใจเท่าไหร่ 555 แล้วก็มี COVID ก็ติดอันดับด้วย&lt;/p&gt;

&lt;p&gt;ทีนี้เราลองหยิบมาดูด้วยตาว่า tag COVID นี่เขาคุยอะไรกันนะครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'โรคติดเชื้อไวรัสโคโรนาสายพันธุ์ใหม่_2019_(COVID-19)'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ถ้าเป็นของไม่กี่วันนี้ก็ดูไม่ค่อยมีอะไรนะครับ เป็นประมาณอัพเดตข่าวกันมากกว่า&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DOiBbAPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/836xbow4bcoig7mt8zhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DOiBbAPK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/836xbow4bcoig7mt8zhi.png" alt="query-covid-text.png"&gt;&lt;/a&gt;&lt;br&gt;
แต่ถ้า scroll ย้อนกลับไปประมาณเดือน 3 ก็จะเห็นกระทู้ประมาณว่า ถ้าติดโควิทจะทำยังไงดี หรือแชร์วิธีการปฏิบัติตัวต่าง ๆ เป็นสังคมที่ค่อนข้าง active เลยครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M6Fo5IFW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p2laztcmpdxafse0f1i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M6Fo5IFW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p2laztcmpdxafse0f1i8.png" alt="query-covid-text-old.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ทีนี้ถ้าจะ explore ดูข้อมูลโดยการไล่ดูตารางแบบนี้คงจะไม่สะดวกใช่ไหมครับ&lt;/strong&gt; ต่อไปเราลองสร้าง graph เพื่อ visualize ข้อมูลออกมาดูกันดีกว่า&lt;/p&gt;

&lt;p&gt;ไม่ต้องไปเปิดโปรแกรมไหนเพิ่มเลยครับ BigQuery มีเครื่องมือที่เอาไว้ช่วย visualize และทำ dashboard แบบง่าย ๆ ไว้อยู่แล้ว ชื่อว่า &lt;strong&gt;Google Data Studio&lt;/strong&gt; เราสามารถเอา result จากคำสั่ง SQL ที่เรารัน เข้าไปใน Data Studio ได้เลย โดยการคลิกที่ปุ่ม &lt;code&gt;สำรวจข้อมูล&lt;/code&gt; ด้านบน result แล้วเลือก &lt;code&gt;สำรวจด้วย Data Studio&lt;/code&gt; ตามในภาพครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AJRn3zFr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v5j66pberh5hu1fbjnqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AJRn3zFr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v5j66pberh5hu1fbjnqw.png" alt="sql-to-data-studio.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ทีนี้เราก็จะเด้งมาที่หน้าของ Data Studio ซึ่งพื้นที่ทางด้านซ้ายนี่จะเป็นที่เอาไว้สร้าง dashboard แบบ drag-and-drop ซึ่งตอนนี้ก็จะค้างข้อมูล "กระทู้ COVID" ที่เรา query เอาไว้เมื่อกี้อยู่ ส่วนด้านขวาคือเป็นเหมือน tools box ครับให้เราสามารถเลือกประเภทของ chart และปรับค่าค่าต่าง ๆ ได้ เช่นตัวแปรที่จะเอามาแสดง จำนวน dimensions สี แสงเงาต่าง ๆ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OgEreAnC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gfbpjjhc51ub2mn68psk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OgEreAnC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gfbpjjhc51ub2mn68psk.png" alt="sql-to-data-studio.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ทีนี้สมมุติว่าผมอยากจะ plot จำนวนของกระทู้ (ซึ่งก็คือกระทู้ใน tag COVID ที่เรา query มาเมื่อกี้) อย่างแรกก็เลือกประเภทของ chart เป็น "อนุกรมเวลา" (time series) แล้วก็ให้ "มิติข้อมูล" (dimension) คือแกน X เป็น &lt;code&gt;created_time&lt;/code&gt; ส่วน "เมตริก" (metric) คือแกน Y เป็น &lt;code&gt;Record Count&lt;/code&gt; ตามภาพ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZtHpWRe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/leqm2tzugerfl0zajktj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZZtHpWRe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/leqm2tzugerfl0zajktj.png" alt="data-studio-setting.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;พอเลือกเสร็จปุ๊ปก็จะเห็นว่าพื้นที่ dashboard ก็จะโหลดใหม่ได้เป็น time series plot จำนวนของกระทู้กับเวลาตามที่เราต้องการ และเป็น interactive ด้วย คือเราสามารถจะเลื่อน cursor ไปชี้ เพื่อให้แสดงรายละเอียดข้อมูลของ data point ออกมาได้&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WjSttGZy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7tj03xs7ydd7w782zdgv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WjSttGZy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7tj03xs7ydd7w782zdgv.png" alt="data-studio-timeseries.png"&gt;&lt;/a&gt;&lt;br&gt;
จะเห็ฺนว่า data studio ช่วย aggregate ข้อมูลให้เราเป็นระดับ "วัน" อัตโนมัติด้วยนะครับ เพราะ &lt;code&gt;created_time&lt;/code&gt; นี่เดิมทีเป็นระดับวินาทีเลย ถ้า plot ไปตรง ๆ ก็จะดูไม่รู้เรื่อง แต่ Data Studio คิดส่วนนี้ไว้ให้เราแล้ว เลยไม่ต้องไปแก้เองเลยครับ&lt;/p&gt;

&lt;p&gt;จะเห็นว่าด้วยเครื่องมือสองอย่างนี้ ช่วยให้เราทำงานกับข้อมูลขนาดใหญ่ได้รวดเร็วมากเมื่อเทียบกับการโหลดข้อมูลไป process ใน Python แล้วยังสามารถ visualize ได้ง่ายมากอีกด้วย แค่ลาก ๆ วาง ๆ แปปเดียว&lt;/p&gt;

&lt;p&gt;แต่ข้อเสียก็คือต้องใช้ SQL คล่องหน่อยครับ และอาจจะไม่สามารถคำนวณบางอย่างที่ซับซ้อน ๆ ได้&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Demo&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ต่อไปขออนุญาตแนะนำ chart ที่ผมลองทำดูเล่น ๆ นะครับ อาจจะไม่สวยหรือไม่สื่อความหมายเท่าไหร่ แต่คิดว่าน่าจะทำให้เห็นภาพมากขึ้นว่าเราสามารถเอา Data Studio ไปใช้ทำอะไรได้บ้างนะครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. จำนวนกระทู้แต่ละ tag ตามเวลา&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ปัญหาความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'หุ้น'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ประสบการณ์ความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ปัญหาชีวิต'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ความรักวัยรุ่น'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rMHqlcnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0p7gna978me6gj4i925m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rMHqlcnz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0p7gna978me6gj4i925m.png" alt="demo-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. สัดส่วนของแต่ละ tag&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ปัญหาความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'หุ้น'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ประสบการณ์ความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ปัญหาชีวิต'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ความรักวัยรุ่น'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8nw-Ryk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vjxkcafztp2nbbt39fs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8nw-Ryk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vjxkcafztp2nbbt39fs7.png" alt="demo-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. ช่วงเวลาที่โพส&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hour_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;created_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'.00น.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;SCRAPE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PANTIP&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ปัญหาความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'หุ้น'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ประสบการณ์ความรัก'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ปัญหาชีวิต'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ความรักวัยรุ่น'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--exurp3dc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ws0woh1voep1ydjsja59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--exurp3dc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ws0woh1voep1ydjsja59.png" alt="demo-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;พอได้ charts ที่เราสนใจออกมาแล้วเราสามารถเอามารวมกัน ให้กลายเป็น dashboard อันหนึ่งได้อีกด้วย ใน Data Studio mode Report&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xBZNsO87--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o0sp9diibtlpptnm06xm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xBZNsO87--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o0sp9diibtlpptnm06xm.png" alt="dashboard.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ซึ่ง dashboard อันนี้ พอทำเสร็จแล้วก็สามารถที่จะแชร์ให้คนอื่นเข้ามาดู หรือเข้ามาแก้ไขได้ (คล้าย ๆ Google Docs นั่นเอง) และยัง update ตามข้อมูลบน BigQuery อัตโนมัติอีกด้วย ไม่ต้องมานั่งสร้าง dashboard ใหม่ทุกครั้งที่เพิ่มข้อมูล สะดวกมาก ๆ ทีเดียว&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;บทความนี้ก็แนะนำ &lt;strong&gt;Google BigQuery&lt;/strong&gt; และ &lt;strong&gt;Data Studio&lt;/strong&gt; สำหรับการ explore ข้อมูลคร่าว ๆ นะครับ ขออภัยถ้าเลือกใช้ chart บางอันผิดประเภทไปหน่อย อย่างเช่น pie chart ในข้อ 2 ควรจะใช้เป็น bar chart มากกว่า ถึงจะเปรียบเทียบความแตกต่างได้ชัดเจนกว่า หรืออย่างข้อ 3 ควรจะ normalize ในแต่ละ tag ก่อน เพื่อให้เทียบสัดส่วนได้ง่ายกว่า เป็นต้น แต่เนื่องจากบทความนี้เป็นแค่ demo เล็ก ๆ เท่านั้น เลยเอาแบบที่ง่าย และให้เห็น chart หลาย ๆ ประเภทมากกว่าครับผม&lt;/p&gt;

&lt;p&gt;สำหรับตัว BigQuery และ Data Studio นี้ ขอแนะนำมาก ๆ เลยครับ ทำงานได้รวดเร็ว เพราะ dashboard ที่ทำด้านบนเนี่ย ใช้เวลาทำประมาณ&lt;u&gt;ไม่ถึงครึ่งชั่วโมง&lt;/u&gt;เท่านั้นครับ สะดวกมาก ๆ แล้วยังมี features ที่น่าสนใจอื่น ๆ อีกเยอะครับ ไว้ในบทความหน้า ๆ จะมาแนะนำกันต่อนะครับ&lt;/p&gt;

&lt;p&gt;ถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;


&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;/p&gt;
&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

</description>
      <category>googlecloud</category>
      <category>tutorial</category>
      <category>datascience</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Python Web Scraping part 4 - ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Thu, 14 May 2020 11:29:38 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4</link>
      <guid>https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4</guid>
      <description>&lt;p&gt;ใน parts ก่อน ๆ เราก็ได้พูดถึงเรื่องการ scrape เว็บแบบง่าย ๆ ไปแล้วนะครับ ซึ่งผู้อ่านบางท่านอาจจะไปลองทำดูเองกับเว็บอื่น ๆ แล้วก็อาจจะพบว่ามันใช้ไม่ได้ ทำไม content สิ่งที่ scrape มา ถึงไม่เหมือนกับที่แสดงบน browser ล่ะ??? หรือบางทีพอจะดูดข้อมูลก็จะพบ error บอกว่า Forbidden บ้าง&lt;/p&gt;

&lt;p&gt;สาเหตุก็เป็นไปได้หลายอย่างครับ อาจจะเป็นเพราะเราส่ง parameter บางอย่างไปไม่ครบ, กำหนด header ไม่ถูก, บนหน้าเว็บมีการใช้ javascript ในการ render, หรือเขาอาจจะหาวิธีกันไม่ให้เรา scrape จริง ๆ ก็ได้&lt;/p&gt;

&lt;p&gt;ในบทความนี้จะขอแนะนำเทคนิคง่าย ๆ ที่อาจจะช่วยให้สามารถ scrape เว็บส่วนใหญ่ได้โดยไม่ติด error page นะครับ และก็จะสาธิตให้ดูโดยการ scrape เว็บต่าง ๆ ด้วยเทคนิคที่แนะนำไปนะครับ&lt;/p&gt;

&lt;p&gt;โค้ดทั้งหมดที่ใช้ในบทความ แชร์ไว้ใน &lt;a href="https://colab.research.google.com/drive/1PCT_Y4GwDwFBy7Q725O7snxe_yqTWFwU?usp=sharing"&gt;Google Colab link นี้&lt;/a&gt; นะครับ สามารถลองรันและแก้โค้ดดูพร้อม ๆ กับอ่านบทความไปได้เลย วิธีการรันก็เขียนอยู่ใน link แล้วนะครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Scraping Techniques&lt;/strong&gt;
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. ถ้าเว็บมีการใช้ javascript เพื่อ render ให้ลองหาตัว API ดู&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เพราะว่าเว็บส่วนใหญ่นะครับ จะไม่ได้ฝังข้อมูลลงในหน้าเว็บตรง ๆ ครับ แต่จะใช้ html เป็นเหมือนแค่ template แล้วใช้ javascript ในการโหลดข้อมูลเข้ามาจาก API ที่ทำแยกไว้ต่างหากแทน ในกรณีนี้แทนที่เราจะโหลดหน้าเว็บหลักที่มีแค่โครง HTML เข้ามา ก็ให้เราโหลดเฉพาะตัวข้อมูลจาก API โดยตรงแทนเลยนั่นเองครับ&lt;/p&gt;

&lt;p&gt;ตัวอย่างเช่น ในเว็บ &lt;a href="https://www.skooldio.com/browse"&gt;www.skooldio.com/browse&lt;/a&gt; ถ้าเราเข้าไปด้วย browser ของเรา ก็จะเห็น course ต่าง ๆ ตามปกติครับ แต่ถ้าเราลอง requests ด้วย python แบบที่เราเคยทำใน parts ก่อน ๆ ดู ก็จะพบว่ามันไม่ได้มีข้อมูลของ course ต่าง ๆ อยู่นะครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.skooldio.com/browse'&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'SQL for Data Analytics'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Output: False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;หรือถ้าเราอยากลองเช็คดูให้แน่ใจ ก็สั่งให้ print HTML ที่ได้มาดูด้วยตาก็ได้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ถ้าลองรันดู ก็จะเห็นว่าเป็นแค่โค้ด HTML กับ css เท่านั้น ไม่ได้มีเนื้อหาอะไรที่เราต้องการอยู่เลย&lt;/p&gt;

&lt;p&gt;เป็นเพราะว่าข้อมูลของ course ต่าง ๆ จะถูกโหลดมาทีหลัง ด้วย API &lt;a href="https://api.skooldio.com/store/graphql"&gt;api.skooldio.com/store/graphql&lt;/a&gt; นั่นเองครับ ดังนั้นเราจึงเปลี่ยนไปดึงข้อมูลจากตัว API แทน อย่างใน code ตัวอย่างด้านล่างผมดึง courses ในหมวดของ "คอร์สออนไลน์" จาก API มาครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://api.skooldio.com/store/graphql'&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"operationName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="s"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"COURSE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"searchParams"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}},&lt;/span&gt;
           &lt;span class="s"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="s"&gt;"persistedQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                            &lt;span class="s"&gt;"sha256Hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2521f2486bdbf3e7d44a5ccb706699e2c8cca1aeaa8f42edd5b17f96a08b7b79"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ตรงนี้ผมใส่ header เข้าไปด้วย เพื่อบอกว่า content เป็นประเภท json นะครับ ถ้าไม่ใส่ตรงนี้ให้ถูกต้องก็จะ error ครับ&lt;/p&gt;

&lt;p&gt;ถ้าเราลอง print ตัว data ออกมาดูก็จะเห็นเป็น dict ที่เก็บข้อมูลของ course ต่าง ๆ เอาไว้อย่างเป็นระเบียบเรียบร้อยแล้วนะครับ นี่ก็ถือเป็นข้อดีอีกอย่าง เวลาที่ web เป้าหมายมี API ให้เราใช้นะครับ 55&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ทีนี้ผู้อ่านอาจจะสงสัยว่าผมทราบได้อย่างไรว่าต้องใส่ payload เป็นอย่างไร และต้องมี headers อะไรบ้างใช่ไหมครับ&lt;/strong&gt; วิธีการก็คือผมใช้ DevTools ตัวหนึ่งของ Google Chrome ชื่อว่า &lt;strong&gt;Chrome Network Inspector&lt;/strong&gt; ในการ monitor network ครับ ว่าตั้งแต่ที่โหลดหน้าเว็บ Skooldio มาเนี่ย มีการโหลดไฟล์อะไรบ้าง ซึ่งเราก็จะสามารถดูได้หมดเลยครับ ตั้งแต่ ไฟล์ชื่ออะไร content เป็นอย่างไร มี headers อะไร payload อะไร สิ่งที่ต้องทำก็แค่ &lt;em&gt;"copy มาถูก"&lt;/em&gt; เท่านั้นครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ซึ่งในบทความนี้ ก็จะสอนว่าจะ &lt;em&gt;"copy มาให้ถูก"&lt;/em&gt; ได้อย่างไรก่อน แล้วในบทความหน้า ๆ เราจะได้เล่นเจ้า &lt;em&gt;Network Inspector&lt;/em&gt; แน่นอนครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OhWAsN5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/43w508kg9ey0g3zmylsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OhWAsN5R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/43w508kg9ey0g3zmylsb.png" alt="network-inspector.png"&gt;&lt;/a&gt;&lt;br&gt;
ภาพ Network Inspector (แทบทางขวา) โชว์ payload ของตัว API&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;2. ลองใส่ user-agent ปลอม&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ในบาง website ก็อาจจะมีกลไกที่ป้องกัน bot ไม่ให้มา scrape ข้อมูลแตกต่างกันไปนะครับ แบบที่ง่ายที่สุดก็คือ block user-agent ครับ&lt;/p&gt;

&lt;p&gt;user-agent เนี่ยเอาไว้บอกกับ server ว่า browser ที่เราใช้อยู่เนี่ยเป็น browser ตัวไหน เช่นเป็น Chrome หรือ Firefox หรือเป็น Safari ใน IOS เป็นต้น ซึ่งมีประโยชน์คือเผื่อบางทีทาง server อาจจะอยาก render หน้าเว็บให้ IOS แตกต่างจาก Android เป็นต้น&lt;/p&gt;

&lt;p&gt;แล้วถ้าเรา requests จาก Python ล่ะ??? คำตอบก็คือ server ก็จะรู้นั่นแหละครับว่าเราเป็น Python user-agent ก็จะเป็นคล้าย ๆ แบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'user-agent'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'python-requests/2.23.0'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เพราะฉะนั้นบาง website อนุญาตให้เข้าถึงได้แค่เฉพาะพวก browser จริง ๆ เท่านั้น&lt;/p&gt;

&lt;p&gt;แต่เราก็มีวิธีหลอก server อยู่ครับ ง่ายมาก ก็แค่แก้ header ตรงนั้นให้เป็น Google Chrome นั่นเอง&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'user-agent'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;มาดูตัวอย่างเว็บกันดีกว่าครับ &lt;br&gt;
อย่างเช่น API การ search ของ Shopee (&lt;a href="https://shopee.co.th/api/v2/search_items"&gt;shopee.co.th/api/v2/search_items&lt;/a&gt;) เป็นต้น&lt;/p&gt;

&lt;p&gt;ก็คือถ้าเรา requests เข้าไปตรง ๆ แบบนี้จะใช้ไม่ได้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://shopee.co.th/api/v2/search_items/?by=relevancy&amp;amp;keyword=ไฟฉาย&amp;amp;limit=50&amp;amp;newest=0&amp;amp;order=desc&amp;amp;page_type=search&amp;amp;version=2'&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ถ้าเป็นในเว็บส่วนใหญ่ก็จะแจ้งเป็น error code ออกมา เช่น 400 Bad Request เป็นต้น&lt;/p&gt;

&lt;p&gt;แต่สำหรับกรณีของ Shopee จะพิเศษหน่อยครับ คือ เราจะยังได้ status code เป็น 200 อยู่ คือไม่มี error นะ แต่ข้อมูลที่ส่งมาจะเป็น None หมดครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'count of results:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'total_count'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Output: status code: 200
#         count of results: None
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Output: {'adjust': None,
#          'algorithm': None,
#          'error': None,
#          'hint_keywords': None,
#          'items': None,
#          'json_data': None,
#          'nomore': None,
#          'price_adjust': None,
#          'query_rewrite': None,
#          'reserved_keyword': None,
#          'show_disclaimer': None,
#          'suggestion_algorithm': None,
#          'total_ads_count': None,
#          'total_count': None,
#          'version': 'b1c94828d525e526ff969f451cc3ac33'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เพราะว่า server กัน user-agent ของเรานั่นเองครับ ในกรณีนี้แค่ใส่ user-agent ใหม่ก็เรียบร้อยครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'user-agent'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'count of results:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'total_count'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="c1"&gt;# Output: status code: 200
#         count of results: 81724
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;3. ลองตรวจสอบ headers อื่น ๆ เพิ่มเติม&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ถ้าทำข้อด้านบน ๆ ไปแล้วยังไม่ได้ผลก็ให้ลองตรวจสอบวิธีการ requests อีกครั้งนะครับ ว่าลืมใส่ headers อะไรไปหรือเปล่า โดยวิธีการตรวจสอบก็คือใช้ &lt;strong&gt;Network Inspector&lt;/strong&gt; ที่พูดถึงไปด้านบนนั่นแหละครับ ดูว่ามี headers ไหนที่อาจจะจำเป็นอีกบ้าง ตรงนี้อาจจะต้องใช้ความรู้เรื่องการทำเว็บเล็กน้อยนะครับ หรือไม่ก็ skill การเดาขั้นเทพนิดนึง 5555&lt;/p&gt;

&lt;p&gt;อย่างเช่นเว็บ &lt;a href="https://pantip.com/tag/%E0%B8%9B%E0%B8%B1%E0%B8%8D%E0%B8%AB%E0%B8%B2%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%A3%E0%B8%B1%E0%B8%81"&gt;pantip&lt;/a&gt; ที่เคย scrape ให้ดูในบทความก่อนนะครับ&lt;/p&gt;

&lt;p&gt;ทีแรกผมก็ลอง requests ไปที่ตัว API ตรง ๆ ดูก่อน&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://pantip.com/api/forum-service/forum/tag_topic?tag_name=ปัญหาความรัก&amp;amp;limit=50'&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: status code: 401
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็พบว่ามันไม่ได้นะครับ&lt;/p&gt;

&lt;p&gt;ก็เลยลองเช็คที่ &lt;strong&gt;Network Inspector&lt;/strong&gt; ดูอีกที ก็เจอว่าใน header มี field ชื่อ &lt;code&gt;authorization&lt;/code&gt; อยู่ด้วย ซึ่งเก็บ credential สำหรับการ access API ไว้อยู่ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_rOAb9sI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zjd07p4sygjrvgh4hm52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_rOAb9sI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/zjd07p4sygjrvgh4hm52.png" alt="pantip-network.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;พอใส่ authorization ไปใน headers ด้วย ก็ใช้ได้เลยครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'authorization'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Basic dGVzdGVyOnRlc3Rlcg=='&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: status code: 200
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ซึ่งจริง ๆ ในหลาย ๆ ครั้ง header authorization เนี่ย ไม่ได้เป็นค่าคงตัวแบบนี้นะครับ บางทีมันอาจจะถูก generate ใหม่เรื่อย ๆ หรืออาจจะต้อง requests ไปขอ token จากอีก endpoint หนึ่งก่อน แล้วอาจจะมีเวลาหมดอายุอีก ต้องคอยขออันใหม่เรื่อย ๆ ก็มี แล้วแต่เว็บเลยครับ &lt;strong&gt;เพราะฉะนั้นการ monitor network เพื่อดูว่า data มัน flow อย่างไรนี่สำคัญมาก แล้วก็ไม่ง่ายเท่าไหร่นะครับ เดี๋ยวจะพูดถึงเพิ่มเติมในบทความเกี่ยวกับ Network นะครับ&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. สร้าง Session เพื่อเข้าถึงข้อมูลสำหรับสมาชิก&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ในบางเว็บ ข้อมูลบางอย่าง page บาง page จะสามารถเรียกดูได้เฉพาะสมาชิกเท่านั้นครับ ถ้าเรา requests ไปโดยที่ยังไม่ได้ login ก็จะไม่สามารถดูข้อมูลนั้นได้ครับ&lt;/p&gt;

&lt;p&gt;วิธีการก็คือ อย่างแรก แน่นอน &lt;em&gt;เราต้องเป็นสมาชิกก่อนครับ&lt;/em&gt; ถึงจะดูได้ ไม่ได้จะสอนให้ hack เว็บเขานะครับ&lt;/p&gt;

&lt;p&gt;ต่อไปเราก็จะใช้ &lt;strong&gt;Session&lt;/strong&gt; ครับ สำหรับการเก็บ state การสร้าง Session ขึ้นมาอันหนึ่งเนี่ย เหมือนเราเปิด web browser ขึ้นมาหน้าหนึ่งแหละครับ มันจะสามารถจำ cookies จำ settings ต่าง ๆ ไว้ให้เราได้ และถ้าทำการ login เสร็จครั้งหนึ่งแล้ว การ request ครั้งต่อ ๆ ไป เว็บเป้าหมายก็จะเห็นว่าเราเป็นสมาชิกแล้วนั่นเองครับ&lt;/p&gt;

&lt;p&gt;ดูตัวอย่างจากเว็บ &lt;a href="https://www.skilllane.com/"&gt;skilllane&lt;/a&gt; นะครับ ที่ใช้เว็บนี้เพราะเราสามารถสมัครสมาชิกได้ง่ายดีครับ สามารถลองสมัครสมาชิกดูแล้วทำตามได้เลยครับ (เขาไม่ได้มาจ้างให้ผมเพิ่มยอดสมาชิกให้นะครับ555)&lt;/p&gt;

&lt;p&gt;โดยเป้าหมายคือหน้า mycourses นะครับ ซึ่งดูได้เฉพาะสมาชิกเท่านั้น&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NYaslkN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ckhack2mzqcwk96obegn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NYaslkN9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ckhack2mzqcwk96obegn.png" alt="skilllane-mycourses.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้า requests เข้าไปตรง ๆ ก็จะได้ status code เป็น 200 นั่นแหละครับ แต่เราไม่สามารถเข้าถึงหน้านั้นได้จริง ๆ สังเกตุได้จาก content ที่ได้มา ไม่มีคำว่า "คอร์สของฉัน" นะครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.skilllane.com/user/mycourses'&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'accessable:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'คอร์สของฉัน'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: status code: 200
#         accessable: False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ดังนั้นเราจึงต้องสร้าง Session ขึ้นมา โดยใช้คำสั่ง &lt;code&gt;requests.Session()&lt;/code&gt; และทำการ login เข้าไปก่อน โดยส่ง requests ผ่าน session ของเราไปที่ endpoint &lt;code&gt;sign_id&lt;/code&gt; ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="c1"&gt;# ใส่ email ที่สมัครไว้
&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# ใส่ password
&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'utf8'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'✓'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'user[email]'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'user[password]'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'user[last_visit_url]'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'user[redirect_method]'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'button'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.skilllane.com/user/sign_in'&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ใช้ session.post แทน requests.post
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'login ok:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: login ok: True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;พอ login สำเร็จแล้ว ตอนนี้ถ้า requests ด้วย session เดิมไป เว็บ skilllane ก็จะจำได้แล้วครับว่าเป็นเรา เพราะฉะนั้นก็จะดูหน้า mycourses ได้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ลองอีกครั้ง
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://www.skilllane.com/user/mycourses'&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ใช้ session.get แทน requests.get
&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'status code:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'accessable:'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'คอร์สของฉัน'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: status code: 200
#         accessable: True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;5. Official API&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เอามาเขียนไว้ข้อ 5 แต่จริง ๆ ก่อนจะเริ่ม scrape เว็บใด ๆ นะครับ ควรเช็คดูก่อนว่าเว็บนั้นมี official API ให้เราใช้หรือเปล่า ถ้ามีก็แค่เอาของเขามาใช้ง่ายกว่าเยอะครับ555&lt;/p&gt;

&lt;p&gt;ซึ่งเว็บดัง ๆ ส่วนใหญ่ก็มีให้ใช้ทั้งสิ้นครับ เช่น Facebook Twitter Instagram Google ไม่ต้องเสียเวลางมหาวิธี scrape เลยครับ&lt;/p&gt;

&lt;p&gt;ข้อเสียก็คือต้องยอมรับ constraints ต่าง ๆ ของ API เขาครับ เช่น Twitter อาจจะ limit ไว้ว่า ใน 15 นาที ห้าม requests มาเกิน "เท่านี้" ครั้งนะ หรืออย่าง Facebook ก็จะไม่อนุญาตให้เก็บข้อมูลในส่วนที่เป็น private เป็นต้น&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. เปลี่ยน IP ด้วย Proxy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;บนเว็บทั่ว ๆ ไปอาจจะไม่ค่อยเจอนะครับ แต่บางเว็บเนี่ย ถ้าเราไป requests มาก ๆ ทำพฤติกรรมผิดปกติ ๆ ฉี่ ๆ เขาถึงขั้นจำ IP เราไว้ว่า IP นี้น่าสงสัย หรืออาจจะดูด้วยบริการของ Google ที่ detect พฤติกรรมการท่องเว็บที่น่าสงสัย แล้วก็ทำการ block ไม่ให้เราใช้งานได้เลยก็ได้นะครับ&lt;/p&gt;

&lt;p&gt;วิธีหนึ่งที่อาจจะช่วยได้บางทีก็คือใช้ &lt;strong&gt;Proxy&lt;/strong&gt; ครับ Proxy นี่เป็นเหมือนคอมพ์อีกเครื่อง ที่มี IP และประวัติการใช้ internet ของตัวเองนะครับ วิธีการใช้ Proxy ก็คือ แทนที่เราจะ requests ไปที่เว็บเป้าหมายโดยตรงนะครับ เราส่ง requests ไปให้ Proxy แทน แล้วให้ Proxy ข่วย requests ไปที่เป้าหมายแทนเราครับ เพราะฉะนั้นทางเว็บเป้าหมายก็จะไม่รู้ว่าจริง ๆ เป็นเครื่องเรา requests ไป&lt;/p&gt;

&lt;p&gt;ซึ่งตัว Proxy นี่จริง ๆ ก็มีของฟรีให้ใช้อยู่นะครับ แต่มันก็จะช้าเพราะต้องแชร์กับคนอื่น ๆ จำนวนมาก แล้วก็อาจจะไม่ได้มีประโยชน์เท่าไหร่ด้วย เพราะคนอื่น ๆ ที่ใช้เนี่ย ส่วนใหญ่ก็คงเป็น bot scrape เหมือนกับเราแหละครับ เพราะงั้น Proxy ฟรี ก็คงโดนแบนไปก่อนตั้งแต่เรายังไม่ได้ใช้ด้วยซ้ำ&lt;/p&gt;

&lt;p&gt;แนะนำว่า &lt;em&gt;ถ้าคิดว่ามันจะช่วยได้จริง ๆ&lt;/em&gt; ก็ซื้อของเสียเงินมาใช้ดีกว่าครับ ซื้อมาลองดูก่อนสักเดือนก็คงไม่ได้แพงเกินไป&lt;/p&gt;

&lt;p&gt;ส่วนวิธีการใช้ สมมุติว่าซื้อมาแล้วและ setup บนเครื่องเราเรียบร้อยแล้ว เราก็จะได้ IP ของ Proxy มานะครับ วิธี requests จาก Python ก็แค่ส่ง proxies เข้าไปตอน requests เท่านั้นเองครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="n"&gt;proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;11.11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;11.11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;11.11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;11.11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ซึ่งปกติเราก็จะซื้อเอาไว้หลาย ๆ IP นะครับ แล้วก็วนลูปเปลี่ยนสลับกันไปเรื่อย ๆ ครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Automated Web Browser&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;สุดท้ายถ้าหลาย ๆ เทคนิคก็ยังไม่ได้ผลอีก ก็มีอีกตัวเลือกหนึ่งครับ คือใช้ web browser นี่แหละทำ แต่แทนที่เรานั่งกดเอง เราเขียน script บอกเอาครับ ว่าจะให้เลื่อน mouse ไปที่ element ไหน รอเป็นเวลากี่วินาที คลิกที่ปุ่มไหน จะให้กดอะไร ให้พิมพ์อะไร ก็เขียน script ให้มันทำให้ได้เลยครับ ถ้าเขียน script ได้เนียนพอ ก็อาจจะสามารถหลอกบางเว็บได้เหมือนกันนะครับ &lt;/p&gt;

&lt;p&gt;สำหรับใน Python ตัวที่ผมใช้ก็คือ &lt;strong&gt;Selenium&lt;/strong&gt; สามารถลองไปศึกษาต่อดูได้ เนื่องจากต้อง setup เยอะ และมีเนื้อหาพอสมควรจะขอยังไม่พูดถึงในบทความนี้นะครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ขอติดเรื่อง &lt;strong&gt;Network Inspector&lt;/strong&gt; กับ &lt;strong&gt;Selenium&lt;/strong&gt; เอาไว้ก่อนนะครับ บทความนี้เอาแค่ให้เห็นภาพเฉย ๆ&lt;/p&gt;

&lt;p&gt;จริง ๆ นอกเหนือจาก 7 ข้อด้านบนแล้ว การ scrape ก็มี techniques อีกมากที่สามารถทำได้นะครับ (มากพอ ๆ techniques การป้องกัน scrape นั่นแหละครับ 555) แต่ผู้เขียนก็ขอย้ำว่าการขอข้อมูลกับเจ้าของอย่างถูกต้องน่าจะดีที่สุดนะครับ แล้วก็เวลาที่ scrape ก็ควรเคารพเจ้าของเว็บไซต์ ไม่ยิง traffic มากเกินไปจนสร้างปัญหาให้เขา หรือเอาข้อมูลไปใช้ในทางที่ไม่ดีนะครับ&lt;/p&gt;

&lt;p&gt;และถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;part อื่น ๆ ใน series&lt;/strong&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part 1: การดูดข้อมูลเบื้องต้น ด้วย Python&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2: Chrome's Code Inspector&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h"&gt;part 3: เทคนิคการ extract ข้อมูลด้วย XPath&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4"&gt;part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf"&gt;part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Google Cloud + Scraping Project: ส่องกระทู้ชาวพันทิปแบบ real-time ด้วย Google Cloud ฟรี (deploy เสร็จสรรพใน 5 นาที!)</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Tue, 28 Apr 2020 11:02:57 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/google-cloud-scraping-project-real-time-google-cloud-deploy-5-51ic</link>
      <guid>https://dev.to/copypasteengineer/google-cloud-scraping-project-real-time-google-cloud-deploy-5-51ic</guid>
      <description>&lt;p&gt;อย่างที่เคยเล่าในบทความก่อน ๆ เรื่องการ scrape ครับ มี use-case หลายอย่างที่สามารถทำได้ เช่น ใช้เพื่อสร้าง dataset สำหรับ train machine learning model ใช้เพื่อทำงานวิจัย หรือใช้ทำ social monitoring เพื่อเก็บรวบรวม comment จากลูกค้าไปพัฒนา product ของบริษัทต่อไป&lt;/p&gt;

&lt;p&gt;บทความนี้เป็นบทความสั้น ๆ (แต่พอเขียน ๆ ไปแล้วก็ไม่สั้นเท่าไหร่ 555) ที่จะทำการ deploy Python script ขึ้นบน Google Cloud เพื่อทำการ scrape เว็บแบบอัตโนมัติ พร้อมทั้งพูดถึง services ต่าง ๆ ของ Google Cloud Platform แบบคร่าว ๆ (คร่าว ๆ จริง ๆ ครับ แล้วถ้ามีโอกาสก็จะมาอธิบายในรายละเอียดของแต่ละ service แยกกันไปอีกที)&lt;/p&gt;

&lt;p&gt;บทความจะแบ่งเป็น 2 ส่วนนะครับ ส่วนแรกจะอธิบายเกี่ยวกับ Google Cloud services และ architecture ที่เราใช้ สั้น ๆ และส่วนที่ 2 ก็จะมี script ให้ สามารถนำไป deploy ได้ทันทีภายใน 5 นาทีเท่านั้นครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;โดยในบทความนี้จะ focus ที่ตัว Google Cloud เป็นหลักนะครับ สำหรับท่านที่สนใจเกี่ยวกับการ scrape ทางเพจของเรา &lt;a href="https://www.facebook.com/CopyPasteEng/"&gt;Copy Paste Engineer&lt;/a&gt; ก็กำลังทำบทความสอนอยู่ ทยอยอัพเรื่อย ๆ สามารถติดตามได้นะครับ &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;อ่าน part 1 ได้ที่นี่&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Cloud Platform&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;สำหรับท่านที่ไม่ทราบ พูดแบบไม่เป็นทางการ ก็คือ Cloud Platform เนี่ย เหมือนเป็น "บ่อ" ใหญ่ ๆ ครับ ที่มี resource อยู่แบบไม่จำกัด หรือมีเยอะมาก เช่นเป็นบ่อของ CPUs ก็มี CPUs กองเอาไว้รวมกันอยู่เต็มไปหมด และก็มีบ่อของ hard disks มีบ่อของ RAMs ...&lt;/p&gt;

&lt;p&gt;ทีนี้ Cloud Platform เจ้าต่าง ๆ เนี่ยก็จะเสนอให้เราเช่า resource ต่าง ๆ ของเขาได้ โดยให้เรา request ขอเช่าไปผ่านหน้าเว็บได้เลย เช่น เราอยากได้ VM สักตัวหนึ่งมา deploy web application ของเรา เราก็แค่เข้าไปที่หน้าเว็บของ Cloud Provider อย่างเช่นใน Google Cloud บนหน้าเว็บเราก็สามารถคลิกเลือก spec ได้ว่าอยากได้ RAM กี่ GB มี CPUs กี่ cores จะเอา hard disk กี่ GB จะเอา OS เป็นอะไร พอกรอกข้อมูลเสร็จก็รอไม่กี่นาที เราก็จะมี VM ตาม spec ที่ระบุไปมาให้ใช้ผ่าน SSH protocol&lt;/p&gt;

&lt;p&gt;ซึ่ง Cloud Provider ก็จะมีระบบ Cloud Manager ที่ใช้จัดการกับ request การเช่า resource ต่าง ๆ ภายใน "บ่อ" ให้อัตโนมัติ คือระบบก็จะไปหา physical resource ตามที่เราต้องการมาเช่น RAM, CPUs, และ disks แล้วก็เอา VM ไปลง จากนั้นก็เปิด access ให้กับเราเข้าใช้งาน และถ้าเราต้องการจะปรับเปลี่ยน spec ของ VM เรา เช่นอยากเพิ่ม RAM จาก 8 GB เป็น 16 GB ก็สามารถทำได้ผ่านหน้าเว็บเช่นกัน ระบบ Cloud Manager ก็อาจจะทำการย้าย VM ของเราไป physical machine ที่มี resource เพียงพอให้เราใช้ได้ภายในเวลาไม่กี่นาที&lt;/p&gt;



&lt;p&gt;และนอกจากการเช่า resource โดยตรง ซึ่งเป็นบริการพื้นฐานของ Cloud Platform แล้ว Cloud Provider แต่ละเจ้าก็จะมี services ที่เป็น high-level ขึ้นมาอีกแตกต่างกันไป&lt;/p&gt;

&lt;p&gt;เช่น &lt;strong&gt;บริการ Platform as a Service (PaaS)&lt;/strong&gt; คือแทนที่เราจะเอา VM เปล่ามา install Python เอง install dependencies ต่าง ๆ setup ต่าง ๆ เอาเอง เราก็แค่บอกบริการ PaaS ว่า เราจาก deploy application ภาษา Python นะ! &lt;em&gt;ทาง Cloud ก็จะจัดเตรียม environment สำหรับการทำ application ภาษา Python ไว้ให้&lt;/em&gt; เราก็แค่เอา code แปะเข้าไป หรือ upload container ขึ้นไปก็ใช้งานได้ทันที&lt;/p&gt;

&lt;p&gt;หรือแทนที่เราจะต้อง &lt;em&gt;install และ setup Database ด้วยตัวเอง&lt;/em&gt; Provider บางเจ้าก็จะมีบริการให้เลย คลิก ๆ สองสามทีก็ได้เครื่อง &lt;strong&gt;VM ที่มี Database&lt;/strong&gt; ติดตั้งไว้เรียบร้อย พร้อมให้ใช้งานได้เลยครับ&lt;/p&gt;

&lt;p&gt;และยังมี service ที่เป็น &lt;strong&gt;Serverless&lt;/strong&gt; ต่าง ๆ ที่เราสามารถ develop application ของเราได้ โดยไม่ต้องกังวลเรื่องตัว VM เลย คือ &lt;em&gt;คิดสะว่าไม่มีเครื่อง&lt;/em&gt; เพราะ Cloud Provider จะเป็นคน manage ตัว VM ให้ทั้งหมด รวมถึงอาจจะทำ auto-scaling ให้ด้วย เป็นต้น&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IhLV3JYN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u8bpo678zxreztbn33p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IhLV3JYN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u8bpo678zxreztbn33p8.png" alt="google-cloud-services.png"&gt;&lt;/a&gt;&lt;br&gt;
ตัวอย่าง Google Cloud services บางส่วน จากหนังสือ &lt;a href="https://subscription.packtpub.com/book/virtualization_and_cloud/9781838647438"&gt;Building Google Cloud Platform Solutions&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Google Cloud Services&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;สำหรับ services ใน Google Cloud Platform ทั้งหมดนี่ก็มีเยอะมากนะครับ ในบทความนี้จะแนะนำเฉพาะ &lt;strong&gt;services ที่ต้องใช้ในการ deploy code ของเรา &lt;em&gt;แบบคร่าว ๆ ไว ๆ&lt;/em&gt;&lt;/strong&gt; เท่านั้นนะครับ ส่วนรายละเอียดของแต่ละอย่างจริง ๆ เนี่ย คิดว่าถ้ามีโอกาสจะแนะนำเพิ่มเติมในบทความต่อ ๆ ไปครับ services ที่นำมาแนะนำได้แก่&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Functions&lt;/li&gt;
&lt;li&gt;Cloud Scheduler&lt;/li&gt;
&lt;li&gt;Cloud Pub/Sub&lt;/li&gt;
&lt;li&gt;Cloud Datastore&lt;/li&gt;
&lt;li&gt;BigQuery&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Cloud Functions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloud Functions เป็น service แบบ serverless ตัวหนึ่ง ที่ช่วยเชื่อม service ต่าง ๆ ของ Google Cloud เข้าด้วยกัน เหมาะสำหรับ deploy script งานเล็ก ๆ ที่จะถูกรันเมื่อมี &lt;strong&gt;&lt;em&gt;"event"&lt;/em&gt;&lt;/strong&gt; บางอย่างครับ ตัวอย่าง &lt;strong&gt;&lt;em&gt;"event"&lt;/em&gt;&lt;/strong&gt; เช่น มี HTTP requests เข้ามาที่ Cloud Function, มีการ update ข้อมูลใน storage, หรือมี message จาก Pub/Sub (เดี๋ยวจะพูดถึงต่อไปครับ)&lt;/p&gt;

&lt;p&gt;ยกตัวอย่างเช่น ในบางครั้งเราอาจจะอยาก export ข้อมูลจาก database ลงใน data warehouse ทุก ๆ วัน เวลาเที่ยงคืน เพื่อให้ฝ่าย BI นำไป analyze ในเช้าวันถัดไปได้ ถ้าต้องให้ Data Engineer ไปรอกด export ข้อมูลเองทุกวันตอนเที่ยงคืน น่าจะลำบากนะครับ วิธีแก้ก็คือเราสามารถเขียน script การ export ข้อมูลใส่ Cloud Functions เอาไว้ แล้วก็ตั้งเวลาให้ทำงานทุก ๆ วันตอนเที่ยงคืน Data Engineer ของเราก็จะมีเวลาพักผ่อนมากขึ้นแล้วครับ&lt;/p&gt;

&lt;p&gt;หรือสมมุติว่าใน Google Cloud Platform ของเรามี VMs อยู่หลายตัว แล้วเราอยากให้ VMs เหล่านี้ เปิดทุก ๆ วันจันทร์ ตอน 9:00น. และปิดทุก ๆ วันศุกร์ ตอน 21:00น. แทนที่เราจะให้พนักงานมานั่งเปิด-ปิดเอง มันก็เสียเวลาใช่ไหมครับ แต่ด้วย Cloud Functions เราสามารถที่จะใส่ script สำหรับ เปิด-ปิด VMs พวกนั้นเอาไว้ แล้วตั้งเวลาให้มันทำงานตามที่กำหนดได้นั่นเอง&lt;/p&gt;

&lt;p&gt;หรือแม้แต่การใช้ Cloud Functions ในการทำเป็น backend endpoint อันเล็ก ๆ สำหรับ query ข้อมูลจาก database, insert data, หรือ clean data ก็ทำได้ครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qzpEbbfo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3adpkilyl69ys0pp1woo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qzpEbbfo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3adpkilyl69ys0pp1woo.png" alt="cloud-function-slack-notify.png"&gt;&lt;/a&gt;&lt;br&gt;
ตัวอย่างการใช้ Cloud Functions ในการทำ Slack notification คือ เมื่อมีคน push commit ใน git ของเรา ให้ Github push &lt;strong&gt;&lt;em&gt;"event"&lt;/em&gt;&lt;/strong&gt; มาที่ Cloud Functions เพื่อให้ Cloud Functions ได้ process และส่งการแจ้งเตือนไปที่ Slack &lt;br&gt;
จาก &lt;a href="https://cloud.google.com/functions"&gt;https://cloud.google.com/functions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;เนื่องจาก เราสามารถ trigger Cloud Functions ให้ทำงานได้หลายทาง &lt;strong&gt;(มี event ที่ใช้ trigger หลายอย่าง) และเราไม่ต้อง manage ตัว server เอง (serverless) สามารถทำ auto-scaling ได้เอง และราคาถูก&lt;/strong&gt; จึงทำให้มีคนนำเอา cloud function ไปประยุกต์ใช้งานหลายอย่างมาก ทั้งในด้าน Data processing, Webhooks, หรือ IoT แต่ Cloud Functions ก็มีข้อจำกัดเล็กน้อยเช่นในเรื่องของขนาด Memory ในแต่ละ instance และ execution time คือ script ต้องใช้ RAM ได้สูงสุดแค่ 2GB และทำการประมวลผลได้มากสุดไม่เกิน 540 วินาทีเท่านั้น&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Cloud Scheduler&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloud Scheduler ก็ตามชื่อเลยครับ คือเป็นตัวตั้งเวลา เพื่อสร้าง &lt;em&gt;&lt;strong&gt;"event"&lt;/strong&gt;&lt;/em&gt; บางอย่างขึ้นมา ซึ่งในที่นี้จะใช้เป็นตัวที่คอยสร้าง &lt;em&gt;&lt;strong&gt;Pub/Sub event&lt;/strong&gt;&lt;/em&gt; เพื่อ trigger Cloud Functions ให้ทำงานครับ (เช่น ทุก ๆ 3 นาที, ทุก ๆ 6 ชั่วโมง, หรือ ทุก ๆ 7 วัน) ถ้าเปรียบเทียบ ก็เหมือนการเขียน cron job บน Linux server นั่นเองครับ &lt;strong&gt;(แถมวิธีการตั้งเวลาก็ใช้เป็นแบบ 5 tokens เหมือนกับ cron เป๊ะ ๆ ด้วย)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZRV1JcJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n3qr2t76099141pqu4w1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZRV1JcJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n3qr2t76099141pqu4w1.png" alt="scheduler-schedule-fields.png"&gt;&lt;/a&gt;&lt;br&gt;
5 tokens ที่ใช้ในการ config Cloud Scheduler จาก &lt;a href="https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules"&gt;https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Cloud Pub/Sub&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;บทบาทของ Cloud Pub/Sub ก็เป็นอย่างที่ได้พูดถึงใน 2 services ข้างต้นนะครับ คือ Cloud Scheduler จะสร้าง event Pub/Sub ขึ้น และตัว event Pub/Sub นี้ ก็จะ trigger ให้ Cloud Functions ทำงาน&lt;/p&gt;

&lt;p&gt;ทีนี้มาพูดถึงตัว Pub/Sub กัน จริง ๆ แล้ว Pub/Sub เนี่ย เป็น message broker เหมือนพวก Kafka อะไรแบบนั้นครับ พูดแบบไม่เป็นทางการก็คือ คล้าย ๆ เป็น &lt;strong&gt;&lt;em&gt;"ไปรษณี"&lt;/em&gt;&lt;/strong&gt; ที่ผู้ส่งสามารถส่งข้อความ หรือข้อมูล ไปหาผู้รับ ที่รออยู่ได้ แต่ต่างกันตรงที่ว่า Pub/Sub จะใช้ &lt;strong&gt;"topic name"&lt;/strong&gt; ในการอ้างอิงครับ&lt;/p&gt;

&lt;p&gt;หมายความว่า สมมุติมีโปรแกรม A รอรับข้อมูล (subscribe) อยู่ที่ topic ชื่อ "scraper-trigger" โปรแกรม A ก็จะรอไปเรื่อย ๆ ครับ จนกว่าจะมีใครส่ง message มาที่ topic "scraper-trigger" นี้ สมมุติ P push ข้อมูล (publish) ไปที่ topic "scraper-trigger" Pub/Sub ก็จะไปเรียกโปรแกรม A ให้ทำงาน พร้อมกับส่งข้อมูลที่ P pushed ไปให้ครับ ซึ่ง topic "scraper-trigger" นั้นก็สามารถมี subscriber หลายคนได้ครับ Pub/Sub ก็จะส่งข้อมูลให้ทุกคนได้รับข้อมูลเดียวกันเหมือนกันหมดนั่นเองครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note: อันนี้เป็นการ subscribe topic แบบหนึ่งเท่านั้นนะครับ ยังมีรูปแบบอื่น แต่จะไว้กล่าวถึงในโอกาสถัดไปครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hvOm3oOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hzm1i5xzjfe14xhwzb0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hvOm3oOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hzm1i5xzjfe14xhwzb0t.png" alt="cloud-pubsub.png"&gt;&lt;/a&gt;&lt;br&gt;
diagram แสดงลำดับการทำงานของ Pub/Sub &lt;br&gt;
จาก &lt;a href="https://cloud.google.com/pubsub/docs/overview"&gt;https://cloud.google.com/pubsub/docs/overview&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Cloud Datastore&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloud Datastore นี่เป็น serverless NoSQL Database ครับ ทำ auto-scale และมี replication ให้ ปกติพวกข้อมูล JSON ที่ต้องอ่านและอัพเดตบ่อย ๆ แต่ไม่รู้จะเอาไว้ไหน เช่นพวก config ค่าต่าง ๆ ผมก็จะเอามาใส่ในนี้แหละครับ เพราะมันไม่ต้อง setup อะไรเลย สามารถใช้งานได้ทันทีและราคาถูก แต่ feature การ query การ update อะไรต่าง ๆ มันจะค่อนข้างน้อยไปหน่อย ถ้าเคยใช้ MongoDB มาก่อนแล้วมา Cloud Datastore ก็อาจจะหงุดหงิดหน่อยนะครับ 555 แต่เข้าใจว่า Google กำลังพัฒนาอยู่ครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;BigQuery&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;BigQuery เป็น data warehouse แบบ serverless ก็คือจะเอาไว้ใช้เก็บ structured data ที่มีการ optimize ให้สามารถ query ได้เร็ว เพื่อนำไปใช้ในการ analyze และทำ dashboard ได้อย่างมีประสิทธิภาพ และเช่นเดียวกัน ก็คือเราไม่ต้อง manage ตัว server ครับ ไม่ต้องสนใจว่ามันจะใช้เครื่องไหนประมวลผลอะไรยังไง เพราะภายในมันจะทำการ auto-scaling และ optimize ให้เองครับ และก็ราคาค่อนข้างถูก&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Pantip Scraper&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;สำหรับตัว Python script ของ scraper เนี่ยจะไม่ลงรายละเอียดลึก ๆ นะครับ เพราะทางเพจก็กำลังมี series Python Web Scraping อยู่แล้ว&lt;/p&gt;

&lt;p&gt;คร่าว ๆ ก็คือเป็น script ที่จะ scrape กระทู้ล่าสุดใน tag &lt;code&gt;ความรักวัยรุ่น&lt;/code&gt; ซึ่งเป็นที่นิยม โดยข้อมูลที่ดูดมาได้แก่ &lt;strong&gt;"ชื่อหัวข้อ"&lt;/strong&gt; ของกระทู้ใน Pantip, id ของหัวข้อ, เวลาที่เขียน, user ที่เขียน, ฯลฯ ออกมาเรื่อย ๆ &lt;strong&gt;จนกว่าจะถึง &lt;em&gt;last_id&lt;/em&gt; ก็คือ id ของหัวข้อที่เคย scrape เอาไว้ล่าสุด&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ดังนั้นสิ่งที่เราต้องการจากระบบของเราก็มีดังนี้&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ส่วนที่ใช้ในการรัน Python script โดยจะตั้ง limit ไว้ให้ scrape มาในแต่ละครั้ง มากที่สุดแค่ 50 กระทู้เท่านั้น&lt;/li&gt;
&lt;li&gt;ส่วนที่ใช้ในการตั้งเวลา เพื่อเรียก Python script ขึ้นมารัน โดยจะตั้งเวลาให้รันทุก ๆ 10 นาที เพราะอัตราการสร้างกระทู้ไม่ได้สูงมาก ไม่มีความจำเป็นต้องทำถี่กว่านี้&lt;/li&gt;
&lt;li&gt;ส่วนที่เก็บ &lt;em&gt;last_id&lt;/em&gt; ของการ scrape ครั้งล่าสุด&lt;/li&gt;
&lt;li&gt;ส่วนที่เก็บข้อมูลผลลัพธ์&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;note: ผู้เขียนจงใจนะครับ ทั้งเรื่องที่จะ scrape แค่ชื่อหัวข้อเท่านั้น, ตั้ง limit จำนวนกระทู้ต่อครั้ง แค่ 50 กระทู้, และตั้งเวลาให้ไม่ถี่มาก เหตุผลก็เพื่อไม่ให้รบกวนเว็บ Pantip ครับ และถ้า scrape ข้อมูลส่วนอื่น ๆ ไปมากกว่านี้คงจะไม่เหมาะสมครับ&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ดังนั้นเราจึงออกแบบ architecture คร่าว ๆ ได้ตามนี้ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8nAeRw0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jt9bwl48nrmrogtw596t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8nAeRw0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jt9bwl48nrmrogtw596t.png" alt="pantip-scrape-architecture.png"&gt;&lt;/a&gt;&lt;br&gt;
คือ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ใช้ &lt;strong&gt;Cloud Functions&lt;/strong&gt; ในการ run Python script&lt;/li&gt;
&lt;li&gt;ใช้ &lt;strong&gt;Cloud Scheduler&lt;/strong&gt; ในการตั้งเวลา เพื่อเรียก Cloud Functions&lt;/li&gt;
&lt;li&gt;ใช้ &lt;strong&gt;Cloud Datastore&lt;/strong&gt; ในการเก็บ &lt;em&gt;last_id&lt;/em&gt; ของการ scrape ครั้งล่าสุด&lt;/li&gt;
&lt;li&gt;ใช้ &lt;strong&gt;BigQuery&lt;/strong&gt; ในการเก็บผลลัพธ์สุดท้าย เผื่อจะเอาไปทำ dashboard หรือ export เป็นไฟล์ csv ออกมาใช้งานต่อได้&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;สิ่งที่ต้องเตรียม&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;account gmail ที่รับ credit Google Cloud $300 ฟรี&lt;/strong&gt; เรียบร้อยแล้ว จาก &lt;a href="https://dev.to/copypasteengineer/google-cloud-300-12oo"&gt;บทความก่อนหน้านี้&lt;/a&gt; ครับ ถ้ายังไม่มีก็ให้ดำเนินการตามในบทความนั้นก่อนนะครับ ถึงจะสามารถ deploy ได้&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Deploy Pantip Scraper&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ผู้เขียนได้เตรียม script ทั้งหมดของระบบไว้ให้ใน &lt;a href="https://colab.research.google.com/drive/1HrTzTlLRKV_BKlYxc_4OqXRJZPUE6Q-N"&gt;Colab notebook นี้&lt;/a&gt; แล้วนะครับ สามารถรันเพื่อได้ทันที พร้อม ๆ กับที่อ่านบทความนี้ครับ วิธีการรันเขียนไว้ใน notebook แล้วนะครับ&lt;/p&gt;

&lt;p&gt;อย่างที่ได้บอกข้างต้นนะครับ เราจะ focus ที่ Cloud Platform เพราะฉะนั้นจะไม่ขออธิบาย Python script ที่ใช้ scrape ในบทความนี้ และจะอธิบายเฉพาะส่วนที่สำคัญนะครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ตรงนี้ไม่เชิงว่าเป็นคำเตือนนะครับ แต่แนะนำว่าขณะที่รันแต่ละคำสั่ง ให้สังเกตุ output ของมันด้วยครับ ถ้ามีตรงไหนที่เหมือนเป็น ERROR ควรหยุดรันทันทีแล้วลองเริ่มรันใหม่ดูแต่แรกนะครับ เพราะเป็นไปได้ว่าอาจจะลืมรันบรรทัดไหนไป&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;ซึ่งจริง ๆ แล้วมันไม่มีอันตรายใด ๆ ทั้งสิ้นแหละครับ เพราะเราใช้ credit free $300 อยู่ แล้ว และราคาของแต่ละ service ที่เราใช้ก็ถูกมาก และถ้ามีปัญหายังไงก็สามารถมาปรึกษาในเพจได้นะครับ&lt;/strong&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;1. สร้าง Python script&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;ขั้นแรกนะครับให้รัน code ในส่วน &lt;strong&gt;เขียน scraping script&lt;/strong&gt; ทั้งหมดก่อนครับ เมื่อทำเสร็จ ไฟล์ script สำหรับ scrape จะถูกสร้างขึ้นครับ ได้แก่ &lt;code&gt;main.py&lt;/code&gt;, &lt;code&gt;requirements.txt&lt;/code&gt;, และ &lt;code&gt;scraper.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;พอรัน ls script จะต้องได้ผลลัพธ์แบบนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;script/
main.py  requirements.txt  scraper.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ซึ่ง script เหล่านี้จะถูก deploy ขึ้นไปที่ &lt;strong&gt;Cloud Functions&lt;/strong&gt; ครับ โดยไฟล์ &lt;code&gt;main.py&lt;/code&gt; จะเป็น script หลักที่จะถูกเรียกไปรัน ส่วน &lt;code&gt;requirements.txt&lt;/code&gt; จะเอาไว้บอก Cloud Functions ว่าเราต้องใช้ module อะไร version เท่าไหร่บ้าง Cloud Functions ก็จะ install รอเอาไว้ให้ครับ&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;2. Login เพื่อใช้ gcloud CLI&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;ต่อไปให้รันคำสั่งนี้เพื่อ login ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud auth login
Go to the following &lt;span class="nb"&gt;link &lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;your browser:

    https://accounts.google.com/o/oauth2/auth?code_challenge&lt;span class="o"&gt;=&lt;/span&gt;...


Enter verification code:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;มันก็จะสร้าง link ขึ้นมา link หนึ่ง ให้กดเข้าไปแล้ว login ด้วย gmail ที่เราเพิ่งได้รับ credit free $300 มาครับ&lt;/p&gt;

&lt;p&gt;จากนั้นมันก็จะ generate code มั่ว ๆ ขึ้นมา ให้ copy code ตรงนั้นมาวางในกล่องสีขาว ๆ หลังคำว่า &lt;code&gt;Enter verification code:&lt;/code&gt; แล้วกด &lt;code&gt;Enter&lt;/code&gt; ครับ &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8OPBToVa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g0knwvc273o3uhxv415m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8OPBToVa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g0knwvc273o3uhxv415m.png" alt="gcloud-verification.png"&gt;&lt;/a&gt;&lt;br&gt;
ตรงนี้คือการยืนยันให้สิทธ์ Colab notebook ที่รันอยู่นี้ให้สามารถ manage Google Cloud Account ของเราได้ครับ&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;3. สร้าง project ใหม่ และผูก project กับ billing account&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;ให้รัน script ในส่วน &lt;strong&gt;สร้าง Project ใหม่&lt;/strong&gt; เพื่อสร้าง project ใหม่ขึ้นมา และจะเห็น output ที่เขียนว่า &lt;code&gt;your new Project ID: ...&lt;/code&gt; ให้ copy Project ID เก็บเอาไว้ด้วยนะครับ เพื่อเวลาที่เราจะลบ project ทิ้ง จะได้ลบถูก project&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UKNouAb9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nc9r7ihng7pr16w9823z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UKNouAb9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nc9r7ihng7pr16w9823z.png" alt="project-name.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;จากนั้นก็รัน script ในส่วน &lt;strong&gt;เปิด Billing Account&lt;/strong&gt; ครับ ตรงนี้ผูก Billing Account ที่เรามี credit ฟรี $300 เข้ากับ project ที่สร้างใหม่นี้ครับ&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;4. ต่อไปก็เปิดใช้งาน services ต่าง ๆ ที่เราใช้ใน project ทั้ง 4 ตัวครับ&lt;/strong&gt;
&lt;/h5&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;cloudscheduler.googleapis.com
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;cloudfunctions.googleapis.com
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;datastore.googleapis.com
&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;bigquery.googleapis.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;5. สร้าง scheduler&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;ให้รันโค้ดในส่วน &lt;strong&gt;ตั้ง Scheduler&lt;/strong&gt; ทั้งหมดครับ คำสั่งนี้ก็จะสร้าง scheduler ขึ้นมา โดยตั้งชื่อ scheduler ตามค่าของตัวแปร &lt;code&gt;SCHEDULER_NAME&lt;/code&gt; ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud scheduler &lt;span class="nb"&gt;jobs &lt;/span&gt;create pubsub &lt;span class="nv"&gt;$SCHEDULER_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PUBSUB_TOPIC_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*/10 * * * *"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--message-body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"start"&lt;/span&gt;  &lt;span class="c"&gt;# ตรงนี้คือตัวข้อความ จะใส่อะไรก็ได้ครับ ไม่สำคัญ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;*/10 * * * *&lt;/code&gt; อันนี้เป็นการตั้งเวลาตามแบบ &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;Cron&lt;/a&gt; ปกติครับ หมายความว่าให้รันทุก ๆ ครั้งที่ เวลามีเลขบอกนาทีหาร 10 ลงตัว (เช่น 13:10น., 09:00น., 21:50น.)&lt;/p&gt;

&lt;p&gt;โดย scheduler ตัวนี้เมื่อทำงาน จะส่ง message ไปที่ Pub/Sub ที่ topic ชื่อ &lt;strong&gt;"scraper-pantip-trigger"&lt;/strong&gt; (ค่าของตัวแปร &lt;code&gt;PUBSUB_TOPIC_NAME&lt;/code&gt;)&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;6. deploy Cloud Functions สะที 555&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;ต่อไปเราก็ deploy Cloud Functions ชื่อ &lt;strong&gt;"pantip-scraper"&lt;/strong&gt; โดยทำงานเมื่อมีข้อความส่งมาที่ Pub/Sub topic ชื่อ &lt;strong&gt;"scraper-pantip-trigger"&lt;/strong&gt; (จากตัวแปร &lt;code&gt;PUBSUB_TOPIC_NAME&lt;/code&gt; ซึ่งเป็นชื่อเดียวกับที่ scheduler ส่ง message ไปหา)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;n | gcloud functions deploy pantip-scraper &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-east2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--trigger-topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PUBSUB_TOPIC_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./script &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--set-env-vars&lt;/span&gt; &lt;span class="nv"&gt;GCLOUD_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GCLOUD_PROJECT&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--entry-point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;python37 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;540
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;โดยให้ใช้ code ที่อยู่ใน folder &lt;code&gt;./script&lt;/code&gt; ที่เราสร้างขึ้นมาตอนแรกสุด และให้รัน function ชื่อ main ใน main.py&lt;/p&gt;

&lt;p&gt;(&lt;code&gt;yes n&lt;/code&gt; นี่ใส่มาเพราะเวลา deploy มันจะถามว่าจะ allow unauthorized access หรือไม่ ใส่ &lt;code&gt;yes n&lt;/code&gt; เพื่อให้ตอบ No ให้เราครับ)&lt;/p&gt;

&lt;p&gt;หลังจาก run code ด้านบนเสร็จหมด เท่านี้ก็ deploy ทุกอย่างเสร็จเรียบร้อยครับ ให้รอสัก 10 นาที หรือรอให้ผ่านเวลาที่หาร 10 ลงตัว เพื่อให้แน่ใจว่า Cloud Functions เราได้ถูกรันแล้วครั้งหนึ่งนะครับ&lt;/p&gt;

&lt;p&gt;จากนั้นก็ให้รันโค้ดบรรทัดต่อไป&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'link to BigQuery table: https://console.cloud.google.com/bigquery?project=...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็จะ generate link ไปหาข้อมูลใน BigQuery ที่ Cloud Functions scrape มาให้ครับ &lt;br&gt;
ถ้าเข้าไปแล้วยังไม่เห็น อาจจะต้องรออีกสักพักแล้วลอง refresh ดูนะครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VmsxihqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5juh7cy0c0fzrttwsz5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VmsxihqR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5juh7cy0c0fzrttwsz5r.png" alt="bigquery-ss.png"&gt;&lt;/a&gt;&lt;br&gt;
ตัวอย่างข้อมูลในตารางที่ scrape ออกมานะครับ&lt;/p&gt;

&lt;p&gt;ส่วนด้านบน สามารถเขียน SQL เพื่อ query ข้อมูลออกมา analyze หรือสร้าง dashboard ได้นะครับ จะพูดถึงในบทความถัด ๆ ไปครับ&lt;/p&gt;
&lt;h5&gt;
  
  
  &lt;strong&gt;7. delete Project&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;สุดท้าย พอเราเล่นจนพอใจแล้วก็ delete project ทิ้งด้วยคำสั่งนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud projects delete &lt;span class="nv"&gt;$GCLOUD_PROJECT&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Pricing&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ถึงจะบอกว่าฟรีก็จริงครับ แต่ลองมาเช็คราคาดูจริง ๆ กันหน่อย ว่าใน service แต่ละตัวถ้ารันจริง ๆ จะต้องใช้เงินเท่าไหร่ต่อเดือนนะครับ&lt;/p&gt;

&lt;p&gt;ซึ่งใน document ของ service แต่ละตัวก็จะมี detail การคิดเงินที่ต่างกันออกไป แล้วก็ซับซ้อน แต่ Google ก็มีบริการ Price Calculator ออกมาให้ เพื่อให้สามารถคำนวณกันเองได้ง่าย ๆ ครับ เข้าไปตามลิ้งนี้เลย &amp;gt; &lt;a href="https://cloud.google.com/products/calculator"&gt;Google Cloud Price Calculator&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cloud Functions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เนื่องจากเราตั้งเวลาให้ Cloud Functions ทำงานทุก 10 นาทีนะครับ เพื่อฉะนั้นจำนวนครั้งการ invocation ใน 1 เดือน ก็จะเป็น 6 * 24 * 30 = &lt;strong&gt;4,320 ครั้ง&lt;/strong&gt; แล้วก็ใส่ spec ของ Functions ที่เราใช้ลงไป ได้แก่ &lt;strong&gt;CPU 400MHz และ RAM 256MB&lt;/strong&gt; ตัว calculator ก็จะคำนวน GiB-seconds และ GHz-seconds ต่อเดือนออกมาให้เรานะครับ &lt;strong&gt;แล้วก็ผมประมาณปริมาณข้อมูลขาออกเป็นครั้งละ 1MB&lt;/strong&gt; พอกด estimate calculator ก็จะบอกว่าใช้เงินเท่าไหร่&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---xbrj6n3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5p6hgssmu6n1kx40x0en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---xbrj6n3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5p6hgssmu6n1kx40x0en.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;$0 ต่อเดือน!&lt;/strong&gt; เพราะว่าแต่ละ service ของ Google เขาก็จะมี quotas ฟรีต่อเดือนให้กับทุกคนอยู่ครับ ซึ่งการใช้งานของเราต่ำกว่า quotas ที่เขาตั้งเอาไว้ เพราะฉะนั้นก็เลยใช้ได้ฟรีครับ&lt;/p&gt;

&lt;p&gt;โดย quotas ฟรีต่อเดือนตรงนี้ไม่เกี่ยวกับ credits $300 ของเรานะครับ หมายความว่าเราสามารถรันตัวนี้ไปได้เรื่อย ๆ โดยที่ยังไม่ต้องเสีย credits $300 นี้ไปด้วยซ้ำครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;BigQuery&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;BigQuery นี้ผมสมมุติให้เราเก็บข้อมูลนี้ไปเรื่อย ๆ แบบนานมาก ๆ เลยนะครับ &lt;strong&gt;จนพื้นที่ storage ของเรามีขนาดเป็น 10GB&lt;/strong&gt; ไปแล้ว (ในปัจจุบันถ้าลองรันทิ้งไว้สัก 1 วัน พื้นที่ storage จริงจะใช้แค่ไม่กี่สิบ kB เท่านั้นครับ เพราะฉะนั้น 10GB นี่เป็น upper bound มาก ๆ ครับ น่าจะต้องเก็บไว้หลายสิบปีอยู่ 5555) แล้วก็ประมาณว่า &lt;strong&gt;ข้อมูลที่อัพโหลดขึ้นไปในแต่ละครั้งเฉลี่ยประมาณ 5 kB&lt;/strong&gt; ดังนั้นในหนึ่งเดือนก็จะมีข้อมูลที่ insert เข้าไปเป็น 4,320 * 5 = 21,600 kB = 21.094 MB ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--De4XYSEb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/33g0shpmb6jy5co44ct1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--De4XYSEb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/33g0shpmb6jy5co44ct1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
เช่นกันก็คือเป็น &lt;strong&gt;$0 ต่อเดือน&lt;/strong&gt; เพราะต่ำกว่า quotas ฟรีที่ให้นะครับ&lt;/p&gt;

&lt;p&gt;service อื่น ๆ ก็เช่นกันครับ อย่าง Scheduler เขาก็ให้รันได้ฟรี 3 ตัวในแต่ละเดือน ส่วน Datastore เขาก็ฟรี storage 1GB และอ่าน/เขียนได้ฟรีเป็นหมื่นครั้งเลยครับ ซึ่งเราใช้เก็บแค่ &lt;code&gt;last_id&lt;/code&gt; เป็นตัวเลขตัวเดียวเท่านั้น ก็ไม่มีทางเกินครับ เช่นเดียวกันกับ Pub/Sub ครับ คือมี quotas ขนาดของ message ตั้ง 10GB ไม่มีทางเกินเช่นกัน&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;บทความนี้อาจจะยาวนิดนึงนะครับ 55 คิดว่าน่าจะพอให้เห็นภาพคร่าว ๆ ของ Google Cloud service บางตัวว่าเอาไปใช้ทำอะไรได้บ้าง&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;แล้วก็ย้ำอีกครั้งนะครับ ตัว scraper นี้เอาไว้เพื่อการทดลองเท่านั้นนะครับ ไม่ควรไปปรับให้มันถี่ขึ้น หรือ scrape ข้อมูลเพื่อไปใช้หากำไรจนทำให้ผู้อื่นเดือดร้อนนะครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;แล้วถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;อ้างอิง:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Functions Pricing - &lt;a href="https://cloud.google.com/functions/pricing"&gt;https://cloud.google.com/functions/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloud Scheduler Pricing - &lt;a href="https://cloud.google.com/scheduler/pricing"&gt;https://cloud.google.com/scheduler/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloud Pub/Sub Pricing - &lt;a href="https://cloud.google.com/pubsub/pricing"&gt;https://cloud.google.com/pubsub/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloud Datastore Pricing - &lt;a href="https://cloud.google.com/datastore/pricing"&gt;https://cloud.google.com/datastore/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;BigQuery Pricing - &lt;a href="https://cloud.google.com/bigquery/pricing"&gt;https://cloud.google.com/bigquery/pricing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Coding ขำ ๆ: ลอง simulate สถานการณ์ COVID-19 แบบง่าย ๆ ด้วย Python part 1</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sun, 19 Apr 2020 05:34:21 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-coding-simulate-covid-19-python-part-1-3l20</link>
      <guid>https://dev.to/copypasteengineer/python-coding-simulate-covid-19-python-part-1-3l20</guid>
      <description>&lt;p&gt;สำหรับสถานการณ์ช่วงนี้ที่ทั้งโลกกำลังเผชิญหน้ากับ COVID-19 เจ้าหน้าที่ที่มีหน้าที่รับผิดชอบในเรื่องนี้คงต้องคิดกันจนปวดหัวนะครับ ว่าจะ take action หรือออก policy อย่างไร ภายใต้ทรัพยากรที่จำกัด เงินจำกัด และคนก็จำกัด ควรจะลงทุนกับการวิจัย vaccine ก่อนดี หรือจะแก้เฉพาะหน้าไปก่อนดี &lt;em&gt;และแต่ละ action จะส่งผลอย่างไร มากหรือน้อย&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ในการจะตอบคำถามต่าง ๆ นั้น ส่วนหนึ่งก็จำเป็นอาศัยการสร้าง model ทางคณิตศาสตร์ที่แม่นยำเพื่อทำนายผลในอนาคตล่วงหน้าเพื่อให้ทราบว่าสถานการณ์ข้างหน้ากำลังจะเป็นไปอย่างไร และถ้าทำอย่างนี้ไป จะเกิดอะไรขึ้น&lt;/p&gt;

&lt;p&gt;ซึ่งงานในส่วนนี้โดยทั่วไปก็จะมี &lt;em&gt;นักระบาดวิทยา (Epidemiologist)&lt;/em&gt; คอยทำหน้าที่ในการเก็บข้อมูลจริง เพื่อมาสร้างโมเดล หรือออกมาตรการรับมือต่าง ๆ อยู่แล้วครับ และยังมี model ทางคณิตศาตร์อื่น ๆ อีกมากที่สามารถใช้ในการทำนายได้จากหลากหลายสายงาน เช่น Data Scientist หรือ Economist ซึ่งสามารถให้ผลที่แม่นยำ และมี performance ในการคำนวณที่ดี เช่นกัน&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rCJDv1b5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph2rmqrwj41z3kaxov1v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rCJDv1b5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph2rmqrwj41z3kaxov1v.png" alt="SEIR-equation.png"&gt;&lt;/a&gt;&lt;br&gt;
ตัวอย่างโมเดลโรคระบาดในมุมของระบาดวิทยา: สมการ differential equation ของ SEIR Model (simplified) &lt;br&gt;
(&lt;a href="https://towardsdatascience.com/social-distancing-to-slow-the-coronavirus-768292f04296"&gt;https://towardsdatascience.com/social-distancing-to-slow-the-coronavirus-768292f04296&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;ในบทความนี้ เพื่อให้เข้าใจถึงความซับซ้อนของปัญหา และปัจจัยต่าง ๆ ที่ต้องคำนึงถึง จึงลองทำเป็น project simulation เล็ก ๆ ง่าย ๆ ขึ้นมาเล่น ๆ ให้มองว่าเป็น project ฝึกฝีมือเขียนโปรแกรมแบบ OOP ขำ ๆ โดยมีเป้าหมายเพื่อศึกษาดูการแพร่กระจายของ virus สมมุติชนิดหนึ่ง ชื่อว่า DIVOC-91 ซึ่งแพร่ระบาดบนโลกสมมุติแห่งหนึ่ง แล้วดูว่าถ้าเราเป็นรัฐบาลหรือเจ้าหน้าที่จะตัดสินใจออกมาตรการรับมืออย่างไร แบบง่าย ๆ ก่อน แล้วจากนั้นจะค่อย ๆ เพิ่มความซับซ้อนของ simulation ขึ้นเรื่อย ๆ ใน part ต่อ ๆ ไป เพื่อให้ใกล้เคียงกับโลกความเป็นจริงมากขึ้นครับ&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Scenario&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ใน part ที่ 1 นี้เพื่อความง่าย เราลองเริ่มจากสร้างโลกขึ้นมาแบบง่าย ๆ โดยการกำหนดเป็น rules ต่าง ๆ ออกมาดังนี้ครับ&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;โลกนี้มีประชากร &lt;strong&gt;7,000 คน&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;คนแต่ละคนจะมี 3 สถานะ คือ &lt;strong&gt;&lt;code&gt;สุขภาพดี&lt;/code&gt;, &lt;code&gt;ติดเชื้อ&lt;/code&gt;, และ &lt;code&gt;เสียชีวิต&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ในแต่ละวัน คนที่ยังไม่เสียชีวิต (แน่นอน 555) แต่ละคนจะเดินทางไปพบเจอคนอื่นที่ยังไม่เสียชีวิต &lt;strong&gt;แบบสุ่ม 10 คน&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ถ้าคนที่ &lt;code&gt;ติดเชื้อ&lt;/code&gt; และคนที่ &lt;code&gt;สุขภาพดี&lt;/code&gt; พบกัน คนที่สุขภาพดีจะมีโอกาสติดเชื้อ &lt;strong&gt;10%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;ในแต่ละวัน คนที่ &lt;code&gt;ติดเชื้อ&lt;/code&gt; จะมีโอกาสเสียชีวิต &lt;strong&gt;0.01%&lt;/strong&gt; (ใจดีมาก)&lt;/li&gt;
&lt;li&gt;แต่ถ้าไม่เสียชีวิต ในแต่ละวัน ผู้ติดเชื้อคนดังกล่าว จะมีโอกาสหาย กลับมาเป็น &lt;code&gt;สุขภาพดี&lt;/code&gt; &lt;strong&gt;1%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;โดยจะเริ่มจากให้มีผู้ติดเชื้อ 1 คน ในวันแรกนะครับ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ใน part นี้ให้เงื่อนไขเป็นแบบง่าย ๆ ตามนี้ก่อน ซึ่งอาจจะดูไม่ค่อยสมจริงบ้าง ใน part ต่อ ๆ ไปก็จะค่อย ๆ เพิ่มเงื่อนไขเข้าไปนะครับ&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;โค้ดของบทความนี้จะแชร์ไว้บน Google Colab ใน &lt;a href="https://colab.research.google.com/drive/1MwCxN9pZUkCt4gNdfCWf0R5bbIa2KaGo"&gt;link นี้&lt;/a&gt; นะครับ เพื่อความสะดวก สามารถแก้โค้ดและรันบน browser ได้ทันที ไม่ต้อง download หรือ install อะไรลงบนเครื่อง ผู้อ่านสามารถรันโค้ดจริงไปพร้อมกับอ่านบทความนี้ได้เลยครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;วิธีการรันโค้ดใน colab notebook&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;ให้คลิกที่ File มุมซ้ายบน แล้วเลือก &lt;code&gt;Save a copy in drive...&lt;/code&gt; ก่อนนะครับ&lt;/li&gt;
&lt;li&gt;จากนั้นให้คลิกเลือก block ที่ต้องการจะรันแล้วกด &lt;code&gt;Ctrl+Enter&lt;/code&gt; นะครับ&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;โดยในบทความจะหยิบเฉพาะโค้ดใน part ที่สำคัญ ๆ เช่น modelling บางส่วนมาอธิบายนะครับ และจะข้ามส่วนที่เป็นรายละเอียดเล็ก ๆ น้อย ๆ ไป เช่น function เสริมบางส่วน หรือการ plot graph แนะนำให้ลองดูโค้ดใน Colab ไปพร้อม ๆ กัน จะช่วยให้เข้าใจมากขึ้นครับ &lt;/p&gt;

&lt;p&gt;เริ่มกันเลยนะครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;ใส่ค่าคงตัวต่าง ๆ&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;จาก rules ต่าง ๆ ที่เรากำหนดขึ้นมา อย่างแรกผมเอาแค่คงตัวต่าง ๆ ใส่เข้าไปในโปรแกรมก่อนครับ ได้แก่&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;จำนวนประชากร (N_POPULATION)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;จำนวนผู้ติดเชื้อเริ่มต้น (N_INFECTED_INIT)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;จำนวนคนที่ไปพบเจอในแต่ละวัน (N_MEETINGS)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;โอกาสติดเชื้อ (PROB_INFECT)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;โอกาสเสียชีวิต (PROB_DIE)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;โอกาสหาย (PROB_CURED)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;โดยจะเก็บเป็น &lt;code&gt;dict&lt;/code&gt; และส่งเป็น parameter เข้าไปให้กับตัว simulator นะครับ เพื่อให้สะดวก เวลาทดลองแก้ค่าต่าง ๆ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;default_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'N_POPULATION'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'N_INFECTED_INIT'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s"&gt;'N_MEETINGS'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s"&gt;'PROB_INFECT'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'PROB_DIE'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'PROB_CURED'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;สร้างตัว Simulator&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ต่อไปเราจะมาสร้างตัว Simulator กันนะครับ โดยตัว Simulator เนี่ย มีหน้าที่วนลูปเพื่ออัพเดตค่าต่าง ๆ ในแต่ละวัน และ monitor status ในแต่ละว่ามีคนสุขภาพดี ติดเชื้อ หรือเสียชีวิตกี่คนแล้ว นะครับ&lt;/p&gt;

&lt;p&gt;อันนี้โค้ดในส่วนของการวนลูปเพื่ออัพเดตค่าต่าง ๆ ในแต่ละวันนะครับ จะใช้ method ชื่อ &lt;code&gt;run()&lt;/code&gt; และรัน input เป็น จำนวนวันที่จะทำการคำนวณ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Simulator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_days&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_days&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meet_friends&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;โดยในแต่ละรอบลูปของ method &lt;code&gt;run()&lt;/code&gt; เนี่ย ก็จะทำการ shuffle list ของคนนะครับ เพื่อให้ลำดับของคนใน list แตกต่างกันออกไปในแต่ละวัน จากนั้น &lt;code&gt;self.meet_friends()&lt;/code&gt; ก็จะทำการสุ่มคนออกมาและจับให้เจอกันนะครับ&lt;/p&gt;

&lt;p&gt;แล้วก็เรียก &lt;code&gt;person.update()&lt;/code&gt; เพื่ออัพเดตสถานะของคนนั้น เช่น อัพเดตสถานะจาก &lt;code&gt;ติดเชื้อ&lt;/code&gt; เป็น &lt;code&gt;สุขภาพดี&lt;/code&gt; หรือ &lt;code&gt;เสียชีวิต&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;สุดท้ายก็ monitor ค่าต่าง ๆ ในวันนั้น ๆ และเก็บเอาไว้เพื่อไป plot graph ตอนจบนะครับ&lt;/p&gt;

&lt;p&gt;อันนี้เป็น method &lt;code&gt;meet_friends()&lt;/code&gt; ก็ไม่มีอะไรซับซ้อน แค่วนลูปคนแต่ละคน แล้ว sample คนออกมา เท่ากับ &lt;code&gt;N_MEETINGS&lt;/code&gt; ก็คือ sample คนออกมา 10 คน แล้วก็ให้เจอกัน โดยเรียกคำสั่ง &lt;code&gt;person.meet_with(friend)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Simulator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;meet_friends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="n"&gt;friends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;people&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'N_MEETINGS'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;friend&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;friends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meet_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;friend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;สร้างคน (Person)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ต่อไปก็จะสร้าง class Person ขึ้นมา โดยสิ่งที่ Person ต้องทำก็จะมี 2 method ที่ต้องเขียน (1) คือ method &lt;code&gt;update()&lt;/code&gt; คือใช้ในการ update สถานะว่าหลังจากติดเชื้อ จะให้ติดเชื้อต่อไป หรือหายดี หรือเสียชีวิต ตามความน่าจะเป็นที่เราได้นิยามเอาไว้ใน scenario และ (2) คือ method &lt;code&gt;meet_with()&lt;/code&gt; ใช้เป็นตัวแทนของการเจอกัน ของคนสองคนนะครับ โดยถ้าหากคนใดคนหนึ่งมีสถานะเป็น &lt;code&gt;ติดเชื้อ&lt;/code&gt; อีกคนจะมีโอกาสติดเชื้อ เป็น &lt;code&gt;PROB_INFECT&lt;/code&gt; ครับ&lt;/p&gt;

&lt;p&gt;เริ่มจาก method &lt;code&gt;update()&lt;/code&gt; มี logic เป็นอย่างนี้ครับ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ถ้าสถานะเป็น &lt;code&gt;เสียชีวิต&lt;/code&gt; หรือ &lt;code&gt;สุขภาพดี&lt;/code&gt;: ไม่ต้องทำอะไร&lt;/li&gt;
&lt;li&gt;สุ่มเลขตั้งแต่ 0 ถึง 1 ถ้ามีค่าน้อยกว่า &lt;code&gt;PROB_DIE&lt;/code&gt; (0.0001) จะเปลี่ยนสถานะเป็น &lt;code&gt;เสียชีวิต&lt;/code&gt; ก็คือจะมีโอกาส 0.01% ที่จะเสียชีวิตนั้นเองครับ&lt;/li&gt;
&lt;li&gt;แต่ถ้าไม่เสียชีวิต: ให้สุ่มเลขตั้งแต่ 0 ถึง 1 ถ้ามีค่าน้อยกว่า &lt;code&gt;PROB_CURED&lt;/code&gt; (0.01) จะเปลี่ยนสถานะกลับเป็น &lt;code&gt;สุขภาพดี&lt;/code&gt; ก็คือจะมีโอกาส 1% ที่จะหายเป็นปกติ ตามที่เรากำหนดเอาไว้ครับ
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_died&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_infected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# random result whether this infected one will die!!!
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;get_binary_random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PROB_DIE'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_died&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="c1"&gt;# random result whether this infected one will be cured!!!
&lt;/span&gt;        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;get_binary_random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PROB_CURED'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cured&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ในโค้ดที่ใช้สุ่มเลขผมสร้างเป็น function ชื่อ &lt;code&gt;get_binary_random()&lt;/code&gt; มาใช้แทนนะครับ เพราะคิดว่ามันสื่อความหมายมากกว่า ซึ่งตัว function ถูกประกาศเอาไว้อยู่ด้านบนนะครับ ตรงส่วน &lt;a href="https://colab.research.google.com/drive/1MwCxN9pZUkCt4gNdfCWf0R5bbIa2KaGo"&gt;util&lt;/a&gt; สามารถเลื่อนขึ้นไปดูได้ครับ&lt;/p&gt;

&lt;p&gt;ต่อไป method &lt;code&gt;meet_with()&lt;/code&gt; ครับ การทำงานก็คือ ถ้ามีคนใดคนหนึ่งติดเชื้อ ส่วนอีกคนไม่ติด ก็จะสามารถมีการเพร่เชื้อได้ครับ โดยมีโอกาสแพร่เชื้อเป็น 10%&lt;/p&gt;

&lt;p&gt;ในส่วนของเงื่อนไขผมเขียนเป็น condition แบบนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_infected&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_infected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;self.is_infected&lt;/code&gt; จะให้ค่าเป็น &lt;code&gt;True&lt;/code&gt; หรือ &lt;code&gt;False&lt;/code&gt; ครับ ซึ่งถ้าเอามาบวกกัน ใน Python &lt;code&gt;True&lt;/code&gt; จะมีค่าเป็นเลข &lt;code&gt;1&lt;/code&gt; และ &lt;code&gt;False&lt;/code&gt; จะมีค่าเป็นเลข &lt;code&gt;0&lt;/code&gt; ในโค้ดจึงใช้การบวกกันแล้วตั้งเงื่อนไขเป็นเมื่อผลบวกเป็น 1 ซึ่งหมายถึงเมื่อมีผู้ติดเชื้อเป็นคนใดคนหนึ่ง แค่หนึ่งคนนั่นเองครับ&lt;/p&gt;

&lt;p&gt;ส่วนในการแพร่เชื้อ ก็ใช้วิธีการสุ่มเลขแบบเดิมครับ ก็คือถ้าค่าสุ่มน้อยกว่า 0.1 สุดท้ายทั้งสองคนก็จะมีสถานะเป็น &lt;code&gt;ติดเชื้อ&lt;/code&gt; ครับ&lt;/p&gt;

&lt;p&gt;ได้โค้ดเต็ม ๆ เป็นดังนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;meet_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# condition: one is healthy and the other is sick (XOR)
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_infected&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_infected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="c1"&gt;# random result whether the healthy one will be infected
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;get_binary_random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PROB_INFECT'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;เท่านี้ก็ได้โค้ดที่ model โครงสร้างของโลกสมมุติ และโรคสมมุติของเราเรียบร้อยแล้วครับ ต่อไปก็มาลอง run จริงกันเลย&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;ลอง Simulate&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;เพื่อความสะดวก ผมสร้าง function สำหรับการรัน simulation ขึ้นมา โดยรับ input เป็น dict ของ configuration ที่ต้องการใช้ และจำนวนวันที่ให้รันนะครับ&lt;/p&gt;

&lt;p&gt;โดยใน function มีการทำงานดังนี้ครับ&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;สร้าง Simulator ขึ้นมา&lt;/li&gt;
&lt;li&gt;add คนเข้าไปใน Simulator โดยให้ คนจำนวน &lt;code&gt;N_INFECTED_INIT&lt;/code&gt; คนแรก มีสถานะเป็นติดเชื้อ (ใน default config ของเราก็คือให้คนที่ 1 เป็นผู้ติดเชื้อนั้นเองครับ)&lt;/li&gt;
&lt;li&gt;run simulation
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_days&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Simulator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;n_population&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'N_POPULATION'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_population&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ทำให้คน N คนแรกมีสถานะ `ติดเชื้อ`
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'N_INFECTED_INIT'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;simulator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ทีนี้ก็เริ่มลองรันจริงดูเลยครับ โดยใช้ default config ที่เรากำหนดเอาไว้ตอนแรกสุดก่อน เป็นเวลา 30 วัน&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;simulator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็ใช้เวลารันสักพักนะครับ จากนั้น result ของ simulation คือ status ของระบบในแต่ละวัน ซึ่งมาจาก function &lt;code&gt;monitor()&lt;/code&gt; สามารถเรียกดูได้แบบนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simulator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ก็จะได้เป็น list ของ dict อันใหญ่ ๆ ออกมา ซึ่งเป็นจำนวนของผู้ที่มีสุขภาพดี จำนวนของผู้ติดเชื้อ และจำนวนผู้เสียชีวิตรวมทั้งหมด ของแต่ละวันนะครับ เพื่อให้ดูง่ายก็จะ plot ออกมาเป็น graph ได้แบบนี้ครับ&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BqUM39rW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yrvu5uwh0avelaq1y318.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BqUM39rW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yrvu5uwh0avelaq1y318.png" alt="result-default-config.png"&gt;&lt;/a&gt;&lt;br&gt;
ในส่วนของการ plot graph ตรงนี้ ผมใช้ module &lt;code&gt;pandas&lt;/code&gt; และ &lt;code&gt;matplotlib&lt;/code&gt; นะครับ ถ้าสนใจสามารถศึกษาดูเพิ่มเติมได้ครับ&lt;/p&gt;

&lt;p&gt;จะเห็นว่าใน 30 วันแรก จำนวนผู้เสียชีวิตไม่ค่อยเพิ่มขึ้นมาเท่าไหร่ เพราะเรากำหนดให้ความน่าจะเป็นในการเสียชีวิตค่อนข้างต่ำครับ แต่เพียงแค่ประมาณ 6 วันแรก แทบจะทุกคนกลายเป็นผู้ติดเชื้อไปเกือบหมดแล้ว ถ้ากลับไปดูจากข้อมูลดิบ &lt;strong&gt;จะเห็นว่าในวันที่ 7 จำนวนผู้ติดเชื้อกลายเป็น 6804 คน จาก 7,000 คน อย่างรวดเร็ว คิดเป็น 97% ภายใน 1 สัปดาห์&lt;/strong&gt; โชคดีนะครับที่ไวรัสตัวนี้อยู่แค่ในโลกสมมุติ 5555&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;สรุปรวมยอดของ DIVOC-91 ใน 1 เดือนแรก เหลือคนสุขภาพดีอยู่ 63 คน เป็นผู้ติดเชื้ออยู่ 6913 คน และเสียชีวิตไปแล้ว 24 คนครับ&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;ลองเป็นรัฐบาล แล้วแก้ปัญหาดูเองเลย!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;คำถามครับ สมมุติว่าเราเป็นรัฐบาลที่ฉลาดครับ รู้ล่วงหน้าตั้งแต่วันแรกละ ว่าในอีก 1 เดือนข้างหน้าจะเกิดอะไรขึ้น แล้วเรามีงบอยู่ก้อนหนึ่ง ถามว่าเราจะทำอย่างไร มี choice ให้ครับ:&lt;br&gt;
&lt;strong&gt;1. ลดการรวมกลุ่ม&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. ปรับปรุงการรักษา&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;3. แจกหน้ากากและเจลล้างมือ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;วิธีการเลือกเนี่ย เราก็ต้องวิเคราะห์ดูว่าแต่ละตัวเลือก จะส่งผลกับ parameter ตัวไหนในโมเดล ให้เปลี่ยนแปลงไปอย่างไรครับ&lt;/p&gt;

&lt;p&gt;ทีนี้สมมุติมีทีมนักวิจัยเอาข้อมูลของแต่ละตัวเลือกมาให้เพิ่ม เป็นอย่างนี้ครับ&lt;br&gt;
&lt;strong&gt;1. ลดการรวมกลุ่ม&lt;/strong&gt; น่าจะส่งผลให้จำนวนของคนที่เจอในแต่ละวัน (&lt;strong&gt;N_MEETINGS&lt;/strong&gt;) ลดลง จาก &lt;strong&gt;10 คน เป็น 6 คน&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. ปรับปรุงการรักษา&lt;/strong&gt; จะช่วยให้ผู้ติดเชื้อเนี่ยเข้าถึงการรักษาได้ง่ายขึ้น และช่วยสนับสนุนเครื่องมือแพทย์ต่าง ๆ จะทำให้โอกาสการหายติดเชื้อ (&lt;strong&gt;PROB_CURED&lt;/strong&gt;) เพิ่มขึ้น จาก &lt;strong&gt;1% เป็น 10%&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. แจกหน้ากากและเจลล้างมือ&lt;/strong&gt; แจกหน้ากากเนี่ยก็จะทำให้เวลาเจอกันมีโอกาสการติดเชื้อต่ำลง และทำความสะอาดด้วยเจลล้างมือก็จะป้องกันไวรัสจากการสัมผัสได้อีก จะทำให้ &lt;strong&gt;PROB_INFECT&lt;/strong&gt; ลดลง จาก &lt;strong&gt;10% เป็น 3%&lt;/strong&gt; เท่านั้น&lt;/p&gt;

&lt;p&gt;ถ้าได้ตัวเลขประมาณแบบนี้ก็สบายแล้วใช่ไหมครับ เราก็สามารถใช้โมเดลที่เรามีมา simulate ดูเอาได้เลย ว่าเมื่อทำแต่ละ choice อีก 30 วัน จะเกิดอะไรขึ้น&lt;/p&gt;

&lt;p&gt;ผมสร้าง function ชื่อ &lt;code&gt;try_new_config()&lt;/code&gt; ขึ้นมา เพื่อให้สามารถแก้ config และรันได้ง่าย ๆ นะครับ โค้ดข้างในก็คือเอาโค้ดที่เรา simulate และ plot มาต่อกันเฉย ๆ ครับ ไม่มีอะไร&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. ลดการรวมกลุ่ม&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;N_MEETINGS: 10 -&amp;gt; 6&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V_C7PCil--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0qz1d9hcils8ovdvs2e8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V_C7PCil--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0qz1d9hcils8ovdvs2e8.png" alt="result-custom-4-2.png"&gt;&lt;/a&gt;&lt;br&gt;
จะเห็นว่าการลดการรวมกลุ่มช่วยชะลอการแพร่เชื้อได้เล็กน้อยครับ สังเกตุได้จากจุดตัดของจำนวนคนสุขภาพดี (สีฟ้า) และจำนวนผู้ติดเชื้อ (สีเหลือง) จะอยู่ที่ประมาณวันที่ 8 ในขณะที่กราฟเดิมของเราจะอยู่ที่ประมาณวันที่ 4 แต่เมื่อผ่านไป 30 วัน จำนวนผู้ป่วยก็ไม่ต่างกันมาก ส่วนจำนวนผู้เสียชีวิตลดลงมาเป็น 19 คน จาก 24 คนครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. ปรับปรุงการรักษา&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PROB_CURED: 0.01 -&amp;gt; 0.1&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LvY4cyIK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pixmhkufl25kdb70475o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LvY4cyIK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pixmhkufl25kdb70475o.png" alt="result-custom-4-1.png"&gt;&lt;/a&gt;&lt;br&gt;
ที่น่าสนใจคือจุดพีคของเส้นจำนวนผู้ติดเชื้อ (สีเหลือง) ต่ำกว่ากราฟเดิม ประมาณ 700 คน เนื่องจากผู้ป่วยฟื้นตัวกลับมาสุขภาพดีได้เร็วมากขึ้นนั่นเองครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. แจกหน้ากากและเจลล้างมือ&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PROB_INFECT: 0.1 -&amp;gt; 0.03&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fKkXkgWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p9c4b4xzdn6duerddmuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fKkXkgWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p9c4b4xzdn6duerddmuq.png" alt="result-custom-3.png"&gt;&lt;/a&gt;&lt;br&gt;
ผลกระทบที่ดูจากกราฟนี้จะ &lt;strong&gt;คล้ายกับมาตรการลดการรวมกลุ่ม&lt;/strong&gt; คือเป็นการชะลอการแพร่เชื้อออกไป แต่ไกลกว่า ทำให้จำนวนผู้เสียชีวิตต่ำกว่ามาก คือแค่ 9 คน จากเดิม 24 คนครับ ถ้ามองหาการแก้ปัญหาเฉพาะหน้าสำหรับ 1 เดือน วิธีนี้ก็อาจจะไม่เลว&lt;/p&gt;

&lt;p&gt;ผลลัพธ์และข้อสรุปที่ได้อาจจะแปลก ๆ ไปสักหน่อย &lt;strong&gt;เพราะตัวเลขพวกนี้ผมมั่วขึ้นมาเองทั้งหมดครับ 5555 ทำให้ไม่สามารถนำไปอ้างอิงในโลกความเป็นจริงได้&lt;/strong&gt; แต่สำหรับในโลกสมมุตินี้ ถ้าเราเป็นรัฐบาลและตัดสินใจโดยใช้โมเดลและข้อมูลการวิจัย จะเห็นว่าเราสามารถเลือกตัดสินใจได้อย่างมีเหตุมีผล และสามารถรู้หรือทำนายล่วงหน้าได้ว่าทำอะไรไปจะเกิดผลกระทบแบบไหน ก็จะสามารถวางแผนเตรียมการต่อไปได้อย่างมีประสิทธิภาพครับ&lt;/p&gt;

&lt;p&gt;ส่วนจะเลือก choice ไหนนั้น ให้ผู้อ่านตัดสินใจเองได้เลยครับ ว่าจะเลือกจากจำนวนผู้เสียชีวิตในระยะสั้น หรือการรักษาในระยะยาว หรือแม้แต่ choice อื่น เช่นเลือกทั้งการรักษาและแจกหน้ากากเป็นต้น ก็สามารถลองเล่นดูได้ใน &lt;a href="https://colab.research.google.com/drive/1MwCxN9pZUkCt4gNdfCWf0R5bbIa2KaGo"&gt;colab notebook&lt;/a&gt; ได้เลยครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ใน part นี้เราก็ได้สร้าง model แบบง่าย ๆ เพื่อ simulate การแพร่กระจายของโรคระบาดไปแล้วนะครับ ซึ่งหลาย ๆ ท่านก็จะเห็นว่าตัว model เอง มีความไม่สมเหตุสมผลอีกมาก เช่น&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;ในแต่ละวัน model ใช้การสุ่มให้คนคนหนึ่งเจอใครก็ได้&lt;/strong&gt;&lt;/em&gt; แต่ในความเป็นจริงแล้วเราอาจจะไม่ได้มีโอกาสเจอทุกคนบนโลก อาจจะเจอได้เฉพาะคนที่อยู่ในบริเวณใกล้เคียงหรือทำงานที่เดียวกันเท่านั้น &lt;strong&gt;นี่อาจจะเป็นสาเหตุให้ DIVOC-91 แพร่กระจายอย่างรวดเร็วเกินจริงครับ&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;ปกติเราอาจจะไม่ได้ออกจากบ้านและเจอคนถึง 10 คนทุกวัน&lt;/strong&gt;&lt;/em&gt; บางวันเราอาจจะแค่ไปจ่ายตลาดแล้วกลับมาบ้านก็ได้&lt;/li&gt;
&lt;li&gt;model นี้เรายังไม่ได้รวมการแพร่กระจายที่อาจจะเกิดขึ้นได้ &lt;em&gt;&lt;strong&gt;จากคนที่อาศัยอยู่ในบ้านเดียวกัน&lt;/strong&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;และอื่น ๆ อีกมาก ซึ่งใน part ต่อ ๆ ไป เราก็จะ implement model ให้ซับซ้อนยิ่งขึ้น เพื่อแก้ไขประเด็นดังกล่าวกันนะครับ&lt;/p&gt;

&lt;p&gt;สุดท้ายนี้ ย้ำอีกสักเล็กน้อยว่าอยากให้มอง project นี้เป็น OOP project แบบขำ ๆ นะครับ ผลลพธ์จาก model ที่เราลองรันเล่นกันในนี้ไม่สามารถเอาไปสรุปหรือแย้งการตัดสินใจของเจ้าหน้าที่จริงที่มีความรู้เฉพาะทางและข้อมูลที่แม่นยำได้นะครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note: ในการวิเคราะห์โดยทั่วไป เพื่อให้คำนวณได้อย่างมีประสิทธิภาพ ปกติก็จะต้อง formulate ออกมาเป็นโมเดลคณิตศาสตร์ และ derive ออกมาเป็นสมการ จะสามารถคำนวณผลลัพธ์ ณ จุดเวลาใด ๆ ได้เร็วกว่า แต่ใน project นี้ตั้งใจจะเขียนเป็น OOP project ครับ และใน part สุดท้ายก็คิดว่าจะ visualize ออกมาภาพเคลื่อนไหวสั้น ๆ อันหนึ่ง ให้เห็นเป็นตัวคนวิ่งไปวิ่งมาจริง ๆ ก็น่าจะสนุกดีครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;และถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;



&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

</description>
      <category>python</category>
      <category>oop</category>
    </item>
    <item>
      <title>Python Web Scraping part 3 - เทคนิคการ extract ข้อมูลด้วย XPath</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sun, 12 Apr 2020 05:35:39 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h</link>
      <guid>https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h</guid>
      <description>&lt;p&gt;มาต่อกันกับ series Python Web Scraping part 3 นะครับ หลังจากที่ใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part ที่แล้ว&lt;/a&gt; เราได้เรียนรู้วิธีการแกะ XPath ของแต่ละ element ออกมาจาก web browser แบบง่าย ๆ กันแล้ว แต่ทีนี้ในกรณีที่ข้อมูลที่เราสนใจมีหลายตำแหน่งมาก &lt;strong&gt;เราคงไม่อยากเสียเวลานั่ง extract แต่ละ element ด้วยมือทั้งหมดใช่ไหมครับ&lt;/strong&gt; (เพราะถ้าจะทำด้วยมือทั้งหมด ก็ก๊อปตัวข้อมูลมาตรง ๆ เลยสะดีกว่า 555)&lt;/p&gt;

&lt;p&gt;ท่านที่พอจะมีประสบการณ์การทำเว็บมาบ้าง พอเห็น XPath สัก 1 หรือ 2 อัน ก็อาจจะเดาได้แล้ว ว่า XPath ที่เหมาะสม ที่น่าจะครอบคลุมทุก ๆ element นั้น ควรจะเป็นอะไร แต่ถึงจะเดาไม่ออกก็ไม่เป็นไรครับ เพราะอย่างบาง website ที่ซับซ้อนขึ้นหน่อย ต่อให้มีประสบการณ์ก็คงเดาไม่ออกในทีแรกเช่นกัน&lt;/p&gt;

&lt;p&gt;สำหรับในบทความนี้ก็จะสอนวิธีหนึ่งที่สามารถทำได้ เพื่อหา XPath ที่ &lt;strong&gt;"น่าจะ"&lt;/strong&gt; ครอบคลุมข้อมูลทั้งหมดที่เราต้องการมานะครับ ซึ่งเป็นวิธีที่ผู้เขียนเองก็ใช้งานอยู่จริงในการ scrape website ในหลาย ๆ ครั้งครับ (ที่บอกว่า &lt;strong&gt;"น่าจะ"&lt;/strong&gt; เนี่ย เพราะว่าในความเป็นจริงไม่มีใครรู้หรอกครับ ว่า website เป้าหมายนี้ ถูกเขียน หรือถูกสร้างมาอย่างไร แต่ด้วยวิธีดังกล่าว+หลักของสถิติ เราก็จะมั่นใจได้ในระดับหนึ่งว่า XPath ของเรามัน &lt;strong&gt;"น่าจะ"&lt;/strong&gt; ดีเพียงพอครับ)&lt;/p&gt;

&lt;p&gt;วิธีหนึ่งที่ผมทำก็คือ sample XPath ออกมาดู เอามา &lt;strong&gt;สำรวจ&lt;/strong&gt; เพิ่มอีกเยอะ ๆ ก็จะช่วยให้เห็นภาพมากขึ้นครับ จากนั้นเราก็จะใช้ syntax ของ XPath ในการ select และระบุ condition เพื่อให้ครอบคลุมกรณีที่เรา sample ออกมา ให้มากที่สุดครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ทบทวนสักเล็กน้อย ใน part นี้เราก็ยังอยู่กับเว็บ Wikipedia &lt;a href="https://th.wikipedia.org/wiki/%E0%B8%A3%E0%B8%B2%E0%B8%A2%E0%B8%8A%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B8%9A%E0%B8%B2%E0%B8%A5%E0%B8%95%E0%B8%B3%E0%B8%9A%E0%B8%A5%E0%B9%83%E0%B8%99%E0%B8%9B%E0%B8%A3%E0%B8%B0%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B9%84%E0%B8%97%E0%B8%A2"&gt;https://th.wikipedia.org/wiki/รายชื่อเทศบาลตำบลในประเทศไทย&lt;/a&gt; กันเหมือนเดิมนะครับ โดยเป้าหมายคือ &lt;strong&gt;หา XPath สำหรับ extract รายชื่อเทศบาลตำบลทั้งหมด&lt;/strong&gt; ซึ่งใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part ที่แล้ว&lt;/a&gt; เราใช้ Chrome Inspector ในการแกะ XPath ของ &lt;strong&gt;"Element" หนึ่ง ๆ&lt;/strong&gt; มาได้แบบง่าย ๆ แล้วนะครับ ในตอนนี้เรากำลังจะหา XPath ที่สามารถ extract ข้อมูลทั้งหมดได้ &lt;strong&gt;(คือครอบคลุมทุก element ไม่ใช่แค่ 1 element)&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Sample XPath มาเพิ่ม&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;อย่างในเว็บเป้าหมาย ถ้าลอง scroll เลื่อนขึ้นลงดู จะเห็นว่ามันมีหลายตาราง เพราะฉะนั้น เพื่อให้เราได้เห็นรูปแบบที่หลากหลาย แล้วก็ครอบคลุมข้อมูลทั้งหมดมากที่สุด ก็ควรจะหยิบ sample ออกมาจากหลาย ๆ ที่ กระจาย ๆ กันหน่อยนั่นเองครับ เช่น เอามาจากตาราง 1 บ้าง จากตาราง 8 บ้าง จากตารางสุดท้ายบ้าง หรือจากหัวตารางบ้าง จากท้ายตารางบ้าง ปน ๆ กันไป &lt;strong&gt;โดยหลักสถิติแล้วยิ่งเราสุ่มมามากเท่าไหร่ เราก็จะยิ่งมั่นใจได้ว่าเราได้เห็น pattern ที่เป็นไปได้ทั้งหมดแล้วมากขึ้นนั่นเองครับ&lt;/strong&gt; (จะเรียกว่าหลักสถิติหรือดวงก็ได้นะครับ 555)&lt;/p&gt;

&lt;p&gt;สำหรับท่านที่ยังไม่เข้าใจว่าจะเอา sample XPath ตรงนี้มาได้อย่างไร &lt;strong&gt;กรุณาอ่าน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2&lt;/a&gt; ด่วน ๆ เลยครับ!&lt;/strong&gt; หรือถ้ามีข้อสงสัย สามารถคอมเม้นถามด้านล่าง หรือในเพจ &lt;a href="https://www.facebook.com/CopyPasteEng/"&gt;Copy Paste Engineer&lt;/a&gt; ได้เลยนะครับ&lt;/p&gt;

&lt;p&gt;ด้านล่างนี้เป็นตัวอย่างที่ผม sample ออกมา&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัดสุดท้าย&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตารางสุดท้าย&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตารางสุดท้าย&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัดสุดท้าย&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;จะเห็นว่า XPath จริง ๆ ของข้อมูลเรา มันไม่ได้ตรงไปตรงมาเสมอไปนะครับ อย่างเช่นตัวเลขของ table และ tr ใน XPath ของแต่ละ sample นั้น ก็อาจจะไม่ได้สัมพันธ์กับเลขตารางหรือเลขบรรทัดของข้อมูลเสมอไป หรือบางทีก็มี element b บ้าง บางทีก็ไม่มี &lt;strong&gt;ซึ่งในเหตุการณ์เช่นนี้สามารถพบเจอได้จริง ในเว็บทั่ว ๆ ไปนะครับ&lt;/strong&gt; โดยอาจเกิดจาก developer ที่เขียนโค้ดไม่สัมพันธ์กันเอง หรืออย่างในกรณีของ Wikipedia นี้ คืออาจเกิดจาก user ซึ่งเป็นผู้เขียนบทความได้ใช้โครงสร้างของข้อมูลที่แตกต่างกันนั่นเองครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. สำรวจ XPath&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ในขั้นตอนนี้ เราจะทำการวิเคราะห์ XPath ที่เรา sample ออกมา แล้วก็ตั้งข้อสังเกตุถึง &lt;strong&gt;จุดที่เหมือน&lt;/strong&gt; และ &lt;strong&gt;จุดที่แตกต่าง&lt;/strong&gt; สำหรับแต่ละตัวอย่างนะครับ ซึ่งอาจจะต้องใช้ความรู้ HTML เล็กน้อยนะครับ แต่จะพยายามอธิบายให้เข้าใจง่ายที่สุดนะครับ&lt;/p&gt;

&lt;p&gt;เมื่อเรา sample ออกมาดูเยอะ ๆ แล้วก็จะเห็นครับว่ามันมีส่วนที่เหมือนกันอยู่ คือ ขึ้นต้นด้วย element &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ซึ่ง &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; ก็หมายถึงตัวตารางแต่ละตารางนั่นเองครับ&lt;/p&gt;

&lt;p&gt;และจะสังเกตุได้จาก XPath ที่ได้มา ว่าข้อมูลที่เราต้องการ&lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; จะเริ่มต้นที่ &lt;strong&gt;ตารางที่ 3&lt;/strong&gt; (เพราะในตารางที่ 1 บรรทัดที่ 1 ใช้ &lt;code&gt;table[3]&lt;/code&gt;) &lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; แล้วตามด้วย &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; &lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; ตามด้วย &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; คือแต่ละ row ของ table นั้น ๆ &lt;br&gt;
&lt;strong&gt;4.&lt;/strong&gt; จากนั้นก็ &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; คือแต่ละ column ของ row นั้น ๆ ซึ่งอาจจะเป็น &lt;strong&gt;column ที่ 2 หรือ column ที่ 3 ก็ได้&lt;/strong&gt; (เช่น sample &lt;code&gt;(4)&lt;/code&gt; และ &lt;code&gt;(5)&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; แล้วก็อาจจะมี หรือไม่มี &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; ก็ได้ (กรณีที่ไม่มี &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; ดู sample ที่ &lt;code&gt;(8)&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6.&lt;/strong&gt; ตามด้วย &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; คือข้อความที่เป็น link&lt;br&gt;
&lt;strong&gt;7.&lt;/strong&gt; แล้วก็อาจจะมี หรือไม่มี &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; ก็ได้&lt;/p&gt;

&lt;p&gt;นี่ก็เป็นข้อสังเกตุทั้งหมดที่เราได้จาก sample ของเรานะครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;3. สร้าง XPath (จริง ๆ สักที 555)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;ต่อไป จากข้อสังเกตุทั้ง 7 ข้อ ด้านบน เราก็ได้ทราบแล้วว่า elements ที่เราต้องการ จะมี XPath ลักษณะอย่างไร &lt;strong&gt;ทีนี้เรานำข้อสังเกตุทั้งหมดมารวมกัน เพื่อสร้าง XPath ขึ้นมา 1 อัน ที่สามารถครอบคลุม cases ที่กล่าวมาได้ทั้งหมด!&lt;/strong&gt; โดยใช้การเขียน &lt;strong&gt;condition&lt;/strong&gt; มาเพื่อช่วยเลือก elements ที่ต้องการนะครับ อันดับแรกก็เริ่มจาก XPath ที่ทุก sample มีร่วมกันก่อน ก็คือ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ทีนี้ ก็จะเริ่มใส่ condition ตามข้อสังเกตุของเราข้างต้นแล้วนะครับ &lt;br&gt;
&lt;strong&gt;1.&lt;/strong&gt; เราต้องการเลือกตารางตั้งแต่ตารางที่ 3 ขึ้นไปเท่านั้น วิธีการคือให้ใส่ condition เข้าไป ว่า &lt;strong&gt;position ของ &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; เนี่ย ต้องมากกว่าหรือเท่ากับ 3 ครับ&lt;/strong&gt; ซึ่งเขียนเป็น condition ได้ว่า &lt;code&gt;position() &amp;gt;= 3&lt;/code&gt; ก็จะได้ XPath เป็น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; ตามด้วย &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; และ &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; ก็เติมเข้าไปตรง ๆ ได้เลยครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; ตามด้วย &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; แต่เราจะเอาเฉพาะ column ที่ 2 หรือ 3 เท่านั้น วิธีหนึ่งที่ทำได้ก็คือ ใส่ condition ว่า &lt;strong&gt;position ของ &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; เนี่ย ต้อง &amp;gt;= 2 และ &amp;lt;= 3&lt;/strong&gt; ซึ่งเขียน condition ออกมาได้ว่า &lt;code&gt;2 &amp;lt;= position() and position() &amp;lt;= 3&lt;/code&gt; จึงได้ XPath เป็น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;mw-c...&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; อย่างที่สังเกตุว่าบาง sample ก็มี &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; ขึ้นก่อนแล้วตามด้วย &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; (&lt;code&gt;/b/a/&lt;/code&gt;) &lt;br&gt;
หรือบาง sample ก็เป็น &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; ขึ้นก่อน แล้วตามด้วย &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; (&lt;code&gt;/a/b/&lt;/code&gt;) &lt;br&gt;
แล้วยังมี &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; แล้ว &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; แล้ว &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; อีก (&lt;code&gt;/b/a/b/&lt;/code&gt;) &lt;br&gt;
โชคดีที่ใน XPath เราสามารถละได้ โดยการเขียนเป็น &lt;code&gt;//a//&lt;/code&gt; จะหมายถึงว่าจะมี element อะไรขึ้นก่อน &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; ก็ได้ (กี่ elements ก็ได้ หรือจะไม่มีก็ได้) แล้วตามด้วย elements อะไรก็ได้ (กี่ elements ก็ได้ หรือจะไม่มีก็ได้) แล้วตัว engine ก็จะหาทุก ๆ elements ที่เป็นไปได้มาให้เองครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;mw-c...&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;note: ซึ่งการละใน XPath นี้ ต้องระวังในการใช้สักเล็กน้อยนะครับ อย่าง &lt;code&gt;//a//&lt;/code&gt; อาจหมายถึง &lt;code&gt;/b/a&lt;/code&gt; ก็ได้ หรือ &lt;code&gt;/b/a/b&lt;/code&gt; ก็ได้ หรืออาจหมายถึง &lt;code&gt;/b/div/a/div/b/a/b/div&lt;/code&gt; ก็ได้ คือในส่วนที่ละ จะแทนเป็นอะไรก็ได้นั่นเองครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; สุดท้ายตามด้วย &lt;code&gt;text()&lt;/code&gt; คือต้องการเอาเฉพาะข้อความที่อยู่ใน element นั้นออกมา&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;mw-c...&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="k"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ได้ XPath สุดท้ายเป็น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="k"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;ซึ่งเป็น XPath ที่เรามั่นใจว่าและครอบคลุมทุกกรณีที่เรา sample ออกมา และ(หวังว่า)จะครอบคลุมข้อมูลที่เราต้องการจริง ๆ ทั้งหมด&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;และเป็น XPath ที่เราก็ได้นำไปใช้จริงแล้วในการ extract &lt;em&gt;รายชื่อเทศบาลตำบล&lt;/em&gt; ใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;บทความ part ที่ 1&lt;/a&gt; นั่นเองครับ&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;สำหรับการสร้าง XPath นั้น วิธีการที่นำเสนอไปก็เป็นหนึ่งในวิธีที่สามารถทำได้ง่าย ๆ ซึ่งเราจะสามารถมั่นใจได้ในระดับหนึ่งว่า XPath ที่ได้มานั้น &lt;strong&gt;ครอบคลุมข้อมูลส่วนใหญ่ โดยทำการ sample ออกมาให้มากขึ้น และ sample &lt;em&gt;ในหลาย ๆ จุดที่แตกต่างกัน กระจาย ๆ กันไป&lt;/em&gt;&lt;/strong&gt; แต่อาจจะต้องอาศัยความรู้ในเรื่อง syntax และคำสั่งของ XPath สักเล็กน้อยสำหรับการเขียน condition เพื่อ select element ต่าง ๆ นะครับ&lt;/p&gt;

&lt;p&gt;ซึ่งจริง ๆ แล้ว &lt;strong&gt;ผู้เขียนเองก็ไม่ได้จำคำสั่งของ XPath ได้ทั้งหมดจริง ๆ หรอกครับ&lt;/strong&gt; เมื่อจำเป็นต้องใช้ที ก็ search หาจาก internet เอา โดยผู้เขียนพบว่า &lt;strong&gt;เว็บ &lt;em&gt;w3school&lt;/em&gt; ได้ทำรายการของคำสั่งต่าง ๆ พร้อมคำอธิบายเอาไว้ได้ดีและครบถ้วนแล้ว ทุก ๆ ท่านสามารถนำไปใช้เพื่ออ้างอิงขณะที่เขียนได้เลยครับ &lt;a href="https://www.w3schools.com/xml/xpath_intro.asp"&gt;link นี้เลยครับ&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;และถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;part อื่น ๆ ใน series&lt;/strong&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part 1: การดูดข้อมูลเบื้องต้น ด้วย Python&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2: Chrome's Code Inspector&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h"&gt;part 3: เทคนิคการ extract ข้อมูลด้วย XPath&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4"&gt;part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf"&gt;part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Python Web Scraping part 2 - Chrome's Code Inspector เว็บบราวเซอร์เรา ทำอะไรได้มากกว่าที่คิด</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sun, 05 Apr 2020 08:25:47 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6</link>
      <guid>https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6</guid>
      <description>&lt;p&gt;ต่อเนื่องจาก &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;บทความในตอนที่แล้ว part 1&lt;/a&gt; เราได้ลองดึงข้อมูลจาก Wikipedia ด้วย Python แบบง่าย ๆ ดูแล้ว ถ้ายังจำได้ หลังจากที่เรา &lt;strong&gt;scrape&lt;/strong&gt; &lt;strong&gt;ข้อมูลดิบ&lt;/strong&gt; เป็นโค้ด HTML ยาว ๆ ลงมาใน Python แล้ว ในส่วนของการ &lt;strong&gt;extract ข้อมูล&lt;/strong&gt; เราใช้ &lt;strong&gt;XPath&lt;/strong&gt; เพื่อ select elements ที่ต้องการออกมา&lt;/p&gt;

&lt;p&gt;ซึ่งผู้เขียนได้แอบบอกไว้แล้วว่า XPath ที่ได้มานั้น มาจากการแกะโค้ด HTML ของเว็บต้นทาง โดยใช้เครื่องมือ &lt;strong&gt;Code Inspector&lt;/strong&gt; บน &lt;strong&gt;Google Chrome Browser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;บทความนี้จะมาพูดถึงเครื่องมือตัวนี้กันครับ ว่ามันจะช่วยให้เราดูดข้อมูลได้ง่ายขึ้นอย่างไรบ้าง&lt;/p&gt;

&lt;p&gt;จริง ๆ แล้วตัว &lt;strong&gt;Code Inspector&lt;/strong&gt; นี่มันเป็นแค่หนึ่งในหลาย ๆ tools ที่ &lt;strong&gt;Google Chrome&lt;/strong&gt; มีให้ใน &lt;strong&gt;Google Chrome DevTools&lt;/strong&gt; เท่านั้นนะครับ โดยจุดประสงค์หลัก ๆ ของ tools ทั้งหลายก็คือใช้เพื่อ debug เว็บที่เราเขียนขึ้นเอง โดยผู้ใช้สามารถเรียกดูได้ทั้งโครงสร้าง การทำงาน และ data ของเว็บที่รันอยู่แบบ real-time เลย &lt;strong&gt;ซึ่งล้วนแล้วแต่เป็น information ที่มีประโยชน์ต่อการ scrape ข้อมูลทั้งสิ้นครับ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;โดยในครั้งนี้จะมาแนะนำ &lt;strong&gt;Code Inspector&lt;/strong&gt; ให้รู้จักกันคร่าว ๆ ว่ามันทำอะไรได้บ้าง แล้วจะใช้เพื่อแกะ XPath ออกมาจากโค้ดได้อย่างไรนะครับ แล้วในบทความถัด ๆ ไป จะสอน tool อีกตัวที่ช่วยให้เราเข้าใจการทำงานของเว็บนั้น ๆ มากขึ้น เช่น กดปุ่มนี้แล้วเว็บจะโหลดข้อมูลอะไรมาจากที่ไหน ด้วย tool ที่ชื่อว่า &lt;strong&gt;Network Inspector&lt;/strong&gt; บน Google Chrome เช่นกันครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ตัว Code Inspector นี่ สำหรับ developer จะเอาไว้ใช้ตรวจสอบว่าหน้าเว็บที่เราเขียนนั้น render ออกมาเป็น code HTML ได้ถูกต้องหรือไม่ ทั้งโครงสร้างของ HTML และ attributes ต่าง ๆ&lt;/p&gt;

&lt;p&gt;&lt;em&gt;note: อธิบายสำหรับท่านที่ไม่ได้เป็น web developer นะครับ บางที web page ที่เราเห็นนั้นอาจจะไม่ได้เขียนด้วย HTML ไฟล์เดียวทั้งหมดนะครับ อาจจะมาจาก HTML หลายไฟล์มารวมกัน หรือถูก render ลงมาเป็น HTML จาก server ต้นทางอีกที ทำให้ในการ develop ฝั่ง server อาจจะต้องดูผลลัพธ์ไปพร้อม ๆ กัน ว่า HTML code ถูก rendered ออกมาตามที่ต้องการหรือไม่&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ซึ่งประโยชน์หลัก ๆ ที่เราจะได้จาก Code Inspector ก็คือใช้หา XPath หรือหาวิธีการ extract ข้อมูลจากหน้าเว็บนั้น ๆ เช่น ดู id ของ elements ที่เราสนใจ&lt;/p&gt;

&lt;p&gt;วิธีการเรียก Code Inspect คือให้เข้าไปที่ website ที่ต้องการ ด้วย &lt;strong&gt;Google Chrome Browser&lt;/strong&gt; แล้วกด &lt;code&gt;F12&lt;/code&gt; ก็จะมีแถบด้านขวา (หรือด้านล่าง) ที่แสดง HTML elements ของ page ปัจจุบัน ปรากฎออกมาตามภาพ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aIFdosyu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sdawbnm8vkmgun33g31u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aIFdosyu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sdawbnm8vkmgun33g31u.png" alt="code-inspector-1.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Highlight ตำแหน่งของ Component จาก Element&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;โดยเราสามารถ &lt;strong&gt;คลิกเพื่อ expand elements ย่อย ๆ ออก&lt;/strong&gt; แล้วเข้าไปดูโครงสร้างที่ซ่อนไว้ได้ หรือถ้าหากลองเอาเมาส์ไปชี้ที่ element ในนั้นดู Chrome ก็จะข่วย highlight บอกเราให้ด้วย ว่า element นั้น อยู่ตรงไหนบนหน้าเว็บ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E1mB_gkv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p7ap5s21e969faqgko4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E1mB_gkv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/p7ap5s21e969faqgko4l.png" alt="code-inspector-highlight.png"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Highlight Element จาก Component&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;หรือในกรณีที่เราอยากทราบว่าตัว component บนหน้าเว็บ เช่น ตารางที่เราเห็นอยู่นี้ มันคือ element ในโค้ด HTML ก็สามารถทำได้ โดยการ &lt;strong&gt;คลิกขวาที่ component นั้น ๆ แล้วเลือก Inspect&lt;/strong&gt; จากนั้นตัว Code Inspector ก็จะไป highlight HTML element ของ component ให้ทันทีครับ&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tT7MQ41b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9qgsvj4nwwmso24tjlp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tT7MQ41b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9qgsvj4nwwmso24tjlp9.png" alt="code-inspector-find-element.png"&gt;&lt;/a&gt; &lt;/p&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;แกะ XPath ของ Component แบบเบสิก&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;เมื่อเราคลิกเลือก element ที่เราสนใจได้แล้ว หากเราอยากจะได้ XPath ของ element นั้น ๆ มา Inspector ก็สามารถหาให้เราแบบง่าย ๆ ได้ (แต่อาจจะไม่ค่อยตรงใจเราสักเท่าไหร่555)&lt;/p&gt;

&lt;p&gt;เช่น สมมุติว่าเราอยากจะได้ XPath ของ &lt;code&gt;เทศบาลตำบลบ้านดู่&lt;/code&gt; ก็ให้หา element ของมันให้ได้ก่อนตามที่อธิบายไว้ด้านบน คือคลิกขวาที่ &lt;code&gt;เทศบาลตำบลบ้านดู่&lt;/code&gt; แล้วเลือก &lt;code&gt;Inspect&lt;/code&gt; Inspector ก็จะ highlight element ของ &lt;code&gt;เทศบาลตำบลบ้านดู่&lt;/code&gt; ไว้ให้ &lt;br&gt;
จากนั้นก็หา XPath โดยการ &lt;strong&gt;คลิกขวาที่ element นั้น แล้วเลือก &lt;code&gt;Copy&lt;/code&gt; แล้วก็ &lt;code&gt;Copy XPath&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lfYxlBnA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m6ht57jk45ms9x0w6owv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lfYxlBnA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m6ht57jk45ms9x0w6owv.png" alt="code-inspector-copy-xpath.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ถ้าลอง paste ดูก็จะได้ XPath ของ &lt;code&gt;เทศบาลตำบลบ้านดู่&lt;/code&gt; แบบนี้ครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;แต่ XPath นี้เป็นแค่ของ &lt;code&gt;เทศบาลตำบลบ้านดู่&lt;/code&gt; เท่านั้นครับ&lt;/strong&gt; แต่สิ่งที่ต้องการจริง ๆ คือ &lt;strong&gt;ชื่อเทศบาลทั้งหมด&lt;/strong&gt; ถามว่าจะหามาได้อย่างไร?&lt;/p&gt;

&lt;p&gt;ถ้ามีประสบการณ์การทำเว็บมาบ้าง พอเห็นข้างบนก็อาจจะเดาได้แล้วว่า XPath ที่เหมาะสมควรจะเป็นอะไรนะครับ แต่ถ้ายังเดาไม่ออกก็ไม่เป็นไรครับ เพราะอย่างบาง website ที่ซับซ้อนขึ้นหน่อย ถึงจะพอมีประสบการณ์ก็คงเดาไม่ออกในทีแรกเช่นกัน&lt;/p&gt;

&lt;p&gt;วิธีหนึ่งที่ผมทำก็คือใช้ตัว Chrome Inspector นี่แหละ เอาตัวอย่างของ XPath จากเทศบาลตำบลอันอื่น ๆ ออกมาดูเพิ่มอีกเยอะ ๆ ก็จะช่วยให้เห็นภาพมากขึ้นครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xpath"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;41&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัดสุดท้าย&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตาราง&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตารางสุดท้าย&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัด&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;@id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mw-content-text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="ow"&gt;div&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;37&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;ตารางสุดท้าย&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;บรรทัดสุดท้าย&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;...แล้วยังไงต่อ...&lt;/strong&gt; ใช่ไหมครับ 555 &lt;br&gt;
ต่อไปเราก็จะสร้าง XPath 1 อัน ที่สามารถครอบคลุมทุกกรณีที่เราเจอมาโดยการใส่ condition เพื่อช่วยเลือก โดยผลลัพธ์สุดท้ายก็จะได้เป็น XPath ที่เราใช้จริง ใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part ที่ 1&lt;/a&gt;  นั่นเองครับ ซึ่งจะอธิบายวิธีการโดยละเอียดต่อใน part 3 นะครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;สำหรับ part ที่ 2 นี้ จุดประสงค์หลัก ก็คือแนะนำ tool ที่ชื่อ &lt;strong&gt;Code Inspector&lt;/strong&gt; ใน &lt;strong&gt;Google Chrome&lt;/strong&gt; ครับ ซึ่งเป็น tool ที่ช่วยให้สามารถ extract ข้อมูลได้ง่ายขึ้นมาก และผู้เขียนได้ใช้หา XPath ไปจริง ๆ ใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;บทความ part 1&lt;/a&gt; ครับ&lt;/p&gt;

&lt;p&gt;บทความนี้เราจะค้างเอาไว้ตรงที่เรา sample XPath ออกมาเยอะ ๆ นะครับ แล้วใน part ที่ 3 ก็จะมาอธิบายให้ฟังว่าจะทำยังไงต่อ เพื่อให้ได้ XPath ที่เราใช้ไปใน &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part ที่ 1&lt;/a&gt;   นะครับ&lt;/p&gt;

&lt;p&gt;หรือถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;/p&gt;


&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;part อื่น ๆ ใน series&lt;/strong&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part 1: การดูดข้อมูลเบื้องต้น ด้วย Python&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2: Chrome's Code Inspector&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h"&gt;part 3: เทคนิคการ extract ข้อมูลด้วย XPath&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4"&gt;part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf"&gt;part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Python Web Scraping part 1 - การดูดข้อมูลเบื้องต้น ด้วย Python</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Sun, 29 Mar 2020 04:22:24 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce</link>
      <guid>https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce</guid>
      <description>&lt;p&gt;ช่วงนี้มีเหตุการณ์ต่าง ๆ เกิดขึ้นถี่มาก ทั้งข่าวการเมืองและโรคระบาด หลาย ๆ ท่านให้ความสนใจกับสถานการณ์เหล่านี้อย่างมาก ว่าจะเป็นอย่างไรต่อไป จำนวนผู้ติดเชื้อจะเพิ่มขึ้นมากน้อยเท่าไหร่ ก็มีการคาดเดาและทำนายกันไปต่าง ๆ นานา&lt;/p&gt;

&lt;p&gt;เนื่องจากเหตุการณ์หลาย ๆ อย่างก็เป็นเรื่องที่ใหม่ และเปลี่ยนแปลงไปมาตลอดเวลา ดังนั้นเพื่อให้สามารถการทำการวิเคราะห์ได้อย่างถูกต้องครบถ้วน ข้อมูลที่แม่นยำและอัพเดตตลอดเวลาจึงเป็นสิ่งจำเป็น เช่น ข้อมูลจากเว็บไซท์ที่อัพเดตตลอดเวลา&lt;/p&gt;

&lt;p&gt;ช่วงหลัง ๆ มานี้ เพื่อน ๆ ของผมหลาย ๆ ท่านที่เป็น &lt;strong&gt;&lt;em&gt;Data Scientist&lt;/em&gt;&lt;/strong&gt; เอง ก็ทักมาถามค่อนข้างบ่อย เกี่ยวกับการทำ &lt;strong&gt;Web Scraping&lt;/strong&gt; เพื่อเก็บข้อมูลที่มีอยู่ใน Internet มาทำการวิเคราะห์ เพราะสำหรับการวิเคราะห์ข้อมูล ซึ่งเป็นงานของ Data Scientist แล้ว สิ่งที่สำคัญที่สุดก็คงจะเป็น &lt;em&gt;ข้อมูล&lt;/em&gt; นั่นแหละครับ &lt;br&gt;
นอกจากนี้ บริษัทใหญ่ ๆ หลาย ๆ แห่ง ก็ยังใช้ประโยชน์จากการทำ Web Scraping ในการเก็บรวบรวมความคิดเห็นของ social เพื่อเอามาวิเคราะห์ปรับปรุงผลิตภัณฑ์ของตัวเองได้อีกด้วย เช่น เก็บ complains ต่าง ๆ เกี่ยวกับผลิตภัณฑ์ในแต่ละชนิด หรือแม้กระทั่งการทำ sentiment analysis ด้วย technique NLP เพื่อดูว่า &lt;em&gt;ความรู้สึก&lt;/em&gt; ของลูกค้าหลังการใช้บริการเป็นอย่างไร จาก comment หรือ post ต่าง ๆ ในโลกออนไลน์แบบ real-time&lt;/p&gt;

&lt;p&gt;ในบทความนี้ก็เลยอยากจะเล่า flow ของการดูดข้อมูลแบบง่าย ๆ ซึ่งเหมาะสำหรับท่านที่พอจะเขียน Python ได้บ้างจนถึงชำนาญ และสนใจศึกษาเกี่ยวกับการทำ Web Scraping ครับ โดยในตัวอย่างนี้จะใช้เว็บ Wikipedia ซึ่งสามารถ scrape ได้ง่าย เหมาะกับผู้เริ่มต้นครับ&lt;/p&gt;

&lt;p&gt;โดยในบทความนี้จะนำเสนอเพียงแค่ basic เพื่อให้ผู้อ่านได้เห็นภาพรวมกว้าง ๆ ก่อน และจะกลับมาอธิบายลงลึกในแต่ละส่วนแยกกันไปในบทความถัด ๆ ไปอีกทีครับ&lt;/p&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ก่อนอื่นจะขอแนะนำเครื่องมือที่ต้องใช้ก่อน จากนั้นก็จะแนะนำ flow ของการ scrape แบบง่าย ๆ พร้อมกับตัวอย่างโค้ดให้นำไปทดลองกันได้ง่าย ๆ ครับ&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;สึ่งที่ต้องเตรียม...&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Python&lt;/strong&gt; แน่นอนว่าถ้าจะเขียน Python script ก็ต้องมีโปรแกรมสำหรับ run script ก่อน ซึ่งตรงนี้สำหรับท่านที่ยังไม่มี Python ในเครื่อง ก็สามารถ install ได้จาก &lt;a href="https://www.python.org/downloads/"&gt;link นี้&lt;/a&gt; โดยแนะนำให้ใช้ version ตั้งแต่ &lt;code&gt;3.6&lt;/code&gt; ขึ้นไปครับ&lt;/p&gt;

&lt;p&gt;แต่ถ้าหากไม่สะดวก install ลงในเครื่องที่ใช้อยู่ก็สามารถ execute Python script online ได้โดยใช้บริการ &lt;strong&gt;Google Colab Notebook&lt;/strong&gt; ของ Google ซึ่งสามารถแก้ไข และ execute Python script ทีละ block ได้ ผ่าน web browser จึงไม่จำเป็นต้อง install หรือ download อะไรลงในเครื่องทั้งสิ้นครับ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;โดยโค้ดทั้งหมดที่ใช้ในบทความนี้จะแชร์ผ่าน Google Colab ด้วยเช่นกัน เนื่องจาก Data Scientist ส่วนใหญ่น่าจะคุ้นเคยกับ interface ลักษณะนี้ดี&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KMRZMmU1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1y2k0fb0qwubrbnfbj1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KMRZMmU1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1y2k0fb0qwubrbnfbj1u.png" alt="google-colab.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพ Google Colab Notebook&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. modules สำหรับการ scrape&lt;/strong&gt; Python modules ที่จำเป็นต้องใช้ในบทความนี้ ได้แก่ &lt;code&gt;requests&lt;/code&gt;, และ &lt;code&gt;lxml&lt;/code&gt; โดยสามารถ install ผ่านโปรแกรม &lt;code&gt;pip&lt;/code&gt; ของ Python ได้จากคำสั่งต่อไปนี้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests lxml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;สำหรับท่านที่ใช้โค้ดบน Google Colab จะมี modules เหล่านี้ installed ไว้อยู่แล้ว ดังนั้นจึงไม่ต้องทำอะไรเพิ่มเติมครับ&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;เริ่มกันเถอะ!&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;code สำหรับบทความนี้จะแชร์ไว้ใน Google Colab ตาม &lt;a href="https://colab.research.google.com/drive/1CxGihPm5if8YGOEjrVo9havGikqqQa6e"&gt;link นี้&lt;/a&gt; นะครับ &lt;br&gt;
ถ้าสะดวกสามารถเปิดโค้ดตามและทดลองแก้ไขและรันดูไปพร้อม ๆ กับที่อ่านบทความได้เลยนะครับ &lt;br&gt;
โดยการคลิกแทบ &lt;code&gt;File&lt;/code&gt; ด้านบนของ Colab แล้วเลือก &lt;code&gt;Save a copy in Drive...&lt;/code&gt; ก็จะสามารถแก้ไขได้ และ save ลง Google Drive ให้อัตโนมัติครับ &lt;br&gt;
ผู้อ่านสามารถรันโค้ดบน Colab ได้โดยการคลิกเลือกแต่ละ block แล้วกด &lt;code&gt;Ctrl+Enter&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Python Web Scraping&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;สมมุติว่า เราต้องการจะดูดข้อมูลจาก &lt;a href="https://th.wikipedia.org/wiki/%E0%B8%A3%E0%B8%B2%E0%B8%A2%E0%B8%8A%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B8%9A%E0%B8%B2%E0%B8%A5%E0%B8%95%E0%B8%B3%E0%B8%9A%E0%B8%A5%E0%B9%83%E0%B8%99%E0%B8%9B%E0%B8%A3%E0%B8%B0%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B9%84%E0%B8%97%E0%B8%A2"&gt;https://th.wikipedia.org/wiki/รายชื่อเทศบาลตำบลในประเทศไทย&lt;/a&gt; เพื่อเก็บรายชื่อของเทศบาลตำบลทั้งหมดในประเทศไทย ก็อาจจะแบ่งได้เป็น 2 ขั้นตอน คือ Scrape และ Extract&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--plYMCEN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ov86mmsfcop0q86h867m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--plYMCEN0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ov86mmsfcop0q86h867m.png" alt="wikipedia-tambon.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Wikipedia Page ที่จะทำการ scrape&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Scrape&lt;/strong&gt; ขั้นตอนแรกก็คือต้องเอาข้อมูลจากหน้าเว็บแบบดิบ ๆ เนี่ย ออกมาให้ได้ก่อน ซึ่งในเว็บอื่น ๆ ทั่ว ๆ ไป ก็จะมีความซับซ้อนหลาย ๆ อย่างที่ทำให้ไม่สามารถดึงออกมาได้ง่าย ๆ ซึ่งจะนำเสนอเทคนิคต่าง ๆ ที่ใช้รับมือแต่ละ cases ในบทความถัด ๆ ไปนะครับ &lt;br&gt;
แต่เนื่องจากในส่วนนี้เราใช้ Web Wikipedia เป็นตัวอย่าง ซึ่งไม่ได้มีการป้องการ scrape หรือกลไกที่ซับซ้อนอะไรมาก เพราะงั้นสามารถส่ง &lt;code&gt;GET request&lt;/code&gt; ไปที่ URL โดยตรงตามโค้ดด้านล่างได้เลยครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://th.wikipedia.org/wiki/รายชื่อเทศบาลตำบลในประเทศไทย'&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: &amp;lt;Response [200]&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;จากโค้ดด้านบนเราจะได้ object ของ &lt;code&gt;Response&lt;/code&gt; มาเก็บไว้ในตัวแปร &lt;code&gt;resp&lt;/code&gt; &lt;br&gt;
โดยเราสามารถเรียกดู content ของ response ที่ได้กลับมาได้โดยใช้ &lt;code&gt;resp.content&lt;/code&gt; หรือ &lt;code&gt;resp.text&lt;/code&gt; แตกต่างกันเล็กน้อย คือ &lt;code&gt;.content&lt;/code&gt; จะให้ค่า content ที่เป็น bytes ออกมา ส่วน &lt;code&gt;.text&lt;/code&gt; จะ decode ข้อมูล bytes ออกเป็น string ให้ จึงสามารถแสดงตัวอักษรภาษาต่าง ๆ ให้ดูได้ เช่นภาษาไทย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;คำสั่งด้านบนจะเรียกดูข้อมูลจำนวน 5,000 ตัวอักษรแรก (limit ไว้เพื่อไม่ให้มันรกเฉย ๆ)&lt;/p&gt;

&lt;p&gt;จะเห็นว่าเราได้โค้ด HTML ของหน้า page นั้น ๆ มาทั้งหมด &lt;br&gt;
นั่นคือตอนนี้เราดึงข้อมูลดิบของหน้าเว็บนั้นมาอยู่ใน Python เรียบร้อยแล้ว&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Extract&lt;/strong&gt; ต่อไปเราจะนำข้อมูลดิบที่ได้มา มากรองให้เหลือแค่ข้อมูลที่เราอยากได้เท่านั้น นั่นก็คือชื่อเทศบาลตำบล &lt;br&gt;
โดยทั่วไปในการ extract ข้อมูล วิธีการที่จะใช้นั้นก็ขึ้นอยู่กับหน้าตาของข้อมูล เช่น เป็น HTML, เป็น JSON, หรือเป็น plain text ซึ่ง modules ใน Python ที่จะนำมาใช้กับข้อมูลแต่ละประเภทก็มีหลายอย่าง เช่น &lt;code&gt;lxml&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;beautifulsoup&lt;/code&gt;, หรือ &lt;code&gt;re&lt;/code&gt; &lt;br&gt;
ในกรณีนี้ เนื่องจากข้อมูลดิบเราเป็น HTML ผู้เขียนจึงใช้ &lt;code&gt;lxml&lt;/code&gt; เนื่องจาก &lt;code&gt;lxml&lt;/code&gt; มีฟังก์ชันที่ใช้แปลงข้อมูล HTML ให้กลายเป็น tree ซึ่งช่วยให้เราสามารถ extract แต่ละ element ใน HTML ได้ง่าย (เป็นความเห็นส่วนตัวนะครับ บางท่านอาจจะถนัดใช้ beautifulsoup มากกว่า ซึ่งอาจจะเขียนถึงในบทความถัด ๆ ไป)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;lxml.etree&lt;/span&gt;

&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lxml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lxml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;etree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTMLParser&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: &amp;lt;Element html at 0x7f34c830e948&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;note: เราต้องระบุ &lt;code&gt;parser&lt;/code&gt; ให้เป็น &lt;code&gt;HTMLParser&lt;/code&gt; เนื่องจากจริง ๆ แล้ว &lt;code&gt;lxml&lt;/code&gt; สามารถใช้กับข้อมูลประเภทอื่นนอกจาก HTML ได้ด้วย&lt;/p&gt;

&lt;p&gt;พอเราแปลงข้อมูลเป็น tree แล้ว ต่อไปเราก็จะสามารถระบุตำแหน่งของ element ที่เก็บข้อมูลที่เราต้องการได้โดยใช้ &lt;code&gt;XPath&lt;/code&gt; &lt;br&gt;
อธิบายแบบคร่าว ๆ &lt;code&gt;XPath&lt;/code&gt; ก็คล้าย ๆ กับ path ของ folder ต่าง ๆ ในเครื่องคอมพิวเตอร์ ที่เอาไว้ระบุตำแหน่งไฟล์หรือ folder ที่เราต้องการ โดยเริ่มจาก &lt;em&gt;root&lt;/em&gt; (C:/) แล้วก็ไปยัง folder ต่าง ๆ ตามลำดับ เช่น &lt;em&gt;C:/Users/CopyPasteEng/Downloads&lt;/em&gt; แต่ &lt;code&gt;XPath&lt;/code&gt; ในที่นี้จะใช้ระบุตำแหน่งของ HTML element บนโค้ด code แทน&lt;br&gt;
ตัวอย่าง &lt;code&gt;XPath&lt;/code&gt; เช่น &lt;code&gt;//div[@id="mw-content-text"]/div/p/text()&lt;/code&gt; อันนี้จะแปลว่า&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. ให้เข้าไปหา element ประเภท div ที่มี id เป็น "mw-content-text"
2. จากนั้นเข้าต่อไปที่ child element ที่เป็นประเภท div
3. แล้วก็เข้าต่อไปที่ element ประเภท p
4. แล้วเอา content ที่เป็น text ทั้งหมดออกมา
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;note: &lt;code&gt;div[...]&lt;/code&gt; คือการใส่เงื่อนไขในการเลือก &lt;code&gt;div&lt;/code&gt; นั้น ๆ เข้าไป ในกรณีด้านบน เงื่อนไขก็คือ attribute &lt;em&gt;id&lt;/em&gt; ต้องเท่ากับ &lt;code&gt;mw-content-text&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ทีนี้คำถามต่อมาก็คือจะเอา &lt;code&gt;XPath&lt;/code&gt; ที่บอกตำแหน่งของข้อมูลที่เราต้องการมาได้อย่างไร &lt;br&gt;
คำตอบก็คือต้องดูจาก code ของ page ครับ ไม่มีทางอื่น 555 &lt;br&gt;
ซึ่งเครื่องมือหนึ่งที่สามารถช่วยให้แกะโค้ดของ web ต่าง ๆ ได้ง่าย ๆ และฟรี ก็คือ &lt;strong&gt;Code Inspector&lt;/strong&gt; บน &lt;strong&gt;Google Chrome&lt;/strong&gt; ครับ สามารถเปิดขึ้นมาได้โดยคลิกขวาที่หน้าเว็บแล้วเลือก &lt;code&gt;Inspect&lt;/code&gt; หรือกด &lt;code&gt;F12&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;กลับมาที่เว็บตัวอย่างของเราครับ &lt;code&gt;XPath&lt;/code&gt; ของชื่อเทศบาลตำบลทั้งหมดที่เราต้องการก็คือ &lt;code&gt;//*[@id="mw-content-text"]/div/table[2 &amp;lt;= position()]/tbody/tr/td[2 &amp;lt;= position() and position() &amp;lt;= 3]//a//text()&lt;/code&gt;* เราจึงสามารถที่จะ extract ข้อมูลออกจาก tree ได้ด้วยคำสั่งต่อไปนี้ ก็จะได้ชื่อของเทศบาลตำบลในประเทศไทยจาก Wikipedia มาเก็บเป็น list เอาไว้ได้ตามต้องการครับ&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;xpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'//*[@id="mw-content-text"]/div/table[2 &amp;lt;= position()]/tbody/tr/td[2 &amp;lt;= position() and position() &amp;lt;= 3]//a//text()'&lt;/span&gt;
&lt;span class="n"&gt;tambon_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tambon_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output: ['เทศบาลตำบลบ้านดู่', 'เทศบาลตำบลเวียงชัย', ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;*เดี๋ยวจะอธิบายที่มาและความหมายของ XPath ตัวนี้ใน part ที่ 3 นะครับ ตอนนี้ของข้ามไปก่อน&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;เพื่อให้บทความกระชับ ผู้จัดทำได้ละรายละเอียดบางส่วนเอาไว้ &lt;br&gt;
เพราะจริง ๆ แล้ว ทั้งในส่วนของการ scrape และ extract ก็มีรายละเอียดอีกมาก และมีเทคนิคที่อาจจะต้องใช้มากมายซึ่งแตกต่างกันตามแต่ละ website เช่น &lt;code&gt;XPath&lt;/code&gt; ที่อยู่ใน code นี่ได้มาได้ยังไง? จะทำอย่างไรกับบาง website ที่มีกลไกที่ป้องกันการ scraping หรือบาง website ที่มี format ขอข้อมูลเป็นลักษณะอื่น&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;เพื่อให้ผู้อ่านได้รับข้อมูลครบถ้วน ในบทความนี้จึงนำเสนอเพียงแค่ basic ให้ผู้อ่านได้เห็นภาพรวมกว้าง ๆ ก่อน และคิดว่าจะกลับมาอธิบายลงลึกในแต่ละส่วนแยกกันไปอีกทีครับ&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;คิดว่าจากบทความนี้ ผู้อ่านน่าจะได้เห็นภาพของการ scrape แบบง่าย ๆ ไปแล้วสำหรับการเริ่มต้น ในบทความถัดไปเราก็จะสามารถไปดูเคสที่ซับซ้อนกันต่อได้ครับ&lt;/p&gt;

&lt;p&gt;อย่างที่ได้กล่าวไปข้างต้นครับ ว่าในการวิเคราะห์ข้อมูลเพื่อทำนายเหตุการณ์ต่าง ๆ ให้ถูกต้องและแม่นยำสิ่งที่สำคัญที่สุดคือ &lt;strong&gt;ข้อมูล&lt;/strong&gt; ที่อัพเดตตลอดเวลา &lt;br&gt;
ซึ่งเราอาจจะมอง Internet เป็นเหมือนแหล่งข้อมูลฟรี ที่เราสามารถ scrape มายังไงก็ได้ &lt;strong&gt;แต่ควรคำนึงถึงเรื่องของกฎหมายและความเหมาะสมด้วยครับ สิ่งสำคัญก็คือต้องไม่ทำให้ใครเดือดร้อน&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;และสำหรับท่านที่สนใจอยากทราบรายละเอียดของแต่ละขั้นตอนอย่างละเอียด ก็สามารถติดตามได้ในบทความที่จะลงต่อ ๆ ไปนะครับ&lt;/p&gt;

&lt;p&gt;หรือถ้ามีเรื่องไหนที่สนใจเพิ่มเติมสามารถ comment เอาไว้ได้นะครับ&lt;/p&gt;

&lt;p&gt;FB Page: &lt;a href="https://www.facebook.com/CopyPasteEng"&gt;Copy Paste Engineer&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

&lt;p&gt;&lt;strong&gt;part อื่น ๆ ใน series&lt;/strong&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-1-python-49ce"&gt;part 1: การดูดข้อมูลเบื้องต้น ด้วย Python&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-2-chrome-s-code-inspector-3ok6"&gt;part 2: Chrome's Code Inspector&lt;/a&gt; &lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-3-extract-xpath-18h"&gt;part 3: เทคนิคการ extract ข้อมูลด้วย XPath&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-4-scrape-7-scrape-4ko4"&gt;part 4: ทำไมถึง scrape บางเว็บไม่ได้??? 7 เทคนิคง่าย ๆ ให้ scrape เว็บส่วนใหญ่ได้ลื่นปรื๊ด&lt;/a&gt;&lt;br&gt;
- &lt;a href="https://dev.to/copypasteengineer/python-web-scraping-part-5-47nf"&gt;part 5: วิธีเลียนแบบการรับส่งข้อมูลของเว็บเป้าหมาย&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>tutorial</category>
      <category>datascience</category>
    </item>
    <item>
      <title>มาใช้ Google Cloud ฟรี 300 ดอลล่าร์กันเถอะ!!!</title>
      <dc:creator>CopyPasteEngineer</dc:creator>
      <pubDate>Mon, 02 Mar 2020 12:31:43 +0000</pubDate>
      <link>https://dev.to/copypasteengineer/google-cloud-300-12oo</link>
      <guid>https://dev.to/copypasteengineer/google-cloud-300-12oo</guid>
      <description>&lt;p&gt;คิดว่าหลาย ๆ ท่านน่าจะเคยได้ยินว่าบริษัทต่าง ๆ ในปัจจุบัน โดยเฉพาะพวก startup มักจะบ่นว่า developer นี่&lt;em&gt;หายาก!&lt;/em&gt; ทั้ง ๆ ที่ตามเว็บต่าง ๆ หรือตามเพจโปรแกรมเมอร์หลาย ๆ กลุ่มก็มี developer อยู่เต็มไปหมด (หลายคนก็ว่างงานด้วยซ้ำ...)&lt;br&gt;&lt;br&gt;
ก็อาจจะเป็นเพราะว่าพวก startup เองเนี่ย มันเกิดขึ้นมาเรื่อย ๆ ยังเป็นเทรนอยู่ ก็มีเจ้าใหม่ ๆ ทั้งเกิดและตายทุกเดือน จึงมีจำนวนมากกว่า developer มาก&lt;br&gt;&lt;br&gt;
หรืออาจจะเป็นเพราะว่า developer เองเนี่ย &lt;em&gt;ไม่มีความสามารถมากพอ&lt;/em&gt; ก็เป็นไปได้ (พูดในมุมมองคนจ้างนะครับ ไม่ใช่ผม 555)&lt;br&gt;&lt;br&gt;
&lt;em&gt;หรืออาจจะเพราะทั้งสองเหตุผล&lt;/em&gt;&lt;br&gt;&lt;br&gt;
แต่ไม่ว่าอย่างไหน ก็ปฎิเสธไม่ได้ว่า ถึงแม้ในด้าน tech จะมีเทรนใหม่ ๆ เกิดขึ้นมามากมาย แต่ developer ก็ยังจำเป็น และมีความต้องการในตลาดสูงไม่เปลี่ยนจริง ๆ&lt;br&gt;&lt;br&gt;
&lt;em&gt;แล้วความสามารถของ developer ในปัจจุบันล่ะ มีความพร้อมแค่ไหน?&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;ทำไมต้องใช้ Cloud&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;สำหรับเด็กจบใหม่หรือคนที่เพิ่งเริ่มศึกษา&lt;/strong&gt; เคยไหมครับที่คิดอยากจะลองศึกษา อยากตามเทคโนโลยีใหม่ ๆ ดู เช่น แค่อยากทดลอง deploy ระบบอะไรบางอย่างให้มันสามารถทำงานได้จริง บน server จริง ๆ หรือทำ prototype ไปเสนองาน แต่พบว่ามันมีปัญหาอื่น ๆ ที่ซับซ้อนตามมา หรือหลาย ๆ ครั้ง จำเป็นต้องใช้เงินจำนวนมาก อย่างเช่น อยากลองทำ Kubernetes ให้ auto-scale ได้ อยากทดลอง false-tolerant แต่มันจำเป็นต้องใช้เงินมากในการซื้อ hardware มาตั้งเอง เพื่อสร้าง cluster ขึ้นมาจริง ๆ ซึ่งไม่คุ้มกับการทดลองแค่ครั้งสองครั้ง หรือต่อให้มี hardware แล้วก็ต้องยุ่งกับการ setup cluster ขึ้นมาซึ่งค่อนข้างวุ่นวาย และไม่เกี่ยวข้อง&lt;br&gt;&lt;br&gt;
ในตอนแรกที่เริ่มศึกษา ผมก็รู้สึกว่า cost ของ hardware อาจจะเป็นเหมือนกำแพงอันใหญ่ ๆ อีกอันนึงสำหรับ new comer ที่เริ่มศึกษาในด้านนี้ก็เป็นไปได้&lt;br&gt;&lt;br&gt;
&lt;strong&gt;ในอีกด้านหนึ่ง สำหรับบริษัท startup เอง&lt;/strong&gt; ถ้าให้เริ่มต้นโดยการซื้อเครื่องแพง ๆ มาเพื่อตั้ง server เอง ก็อาจจะไม่เหมาะ เพราะ startup เองก็มีความเสี่ยงสูง คือไม่รู้ว่าจะมีลูกค้ามาเท่าไหร่&lt;br&gt;&lt;br&gt;
เช่น ถ้าเราซื้อ server มาน้อยไป user เยอะกว่ามาก application ก็อาจจะล่ม หรือถ้าซื้อมาเยอะกว่า user ก็ไม่คุ้มอีก&lt;br&gt;&lt;br&gt;
แล้วยังมีกรณีที่ลูกค้าเลิกเห่อในตอนหลังอีก อย่างเกมส์ Pokemon GO ที่ตอนแรก ๆ user เยอะมาก แต่ผ่านไปสักพัก กลับเหลือคนเล่นอยู่แค่บางกลุ่มเท่านั้น ในกรณีนี้เราอาจจะซื้อ hardware มาตั้งเองเยอะ ๆ ให้รับ user ในช่วงแรกได้ก็จริง แต่หลังจากที่คนเล่นเลิกเห่อแล้ว จะทำยังไงกับ hardware พวกนั้น?&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lE6OENct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qxsyws1163womtkaodrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lE6OENct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qxsyws1163womtkaodrd.png" alt="pokemon-go-dau.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;จำนวน active users ของเกมส์ Pokemon GO ในช่วงปี 2016-2017 ที่ลดลงจากช่วงแรกมาก&lt;br&gt;ที่มา: &lt;a href="https://www.forbes.com/sites/insertcoin/2017/02/28/gen-2-caused-a-huge-spike-in-pokemon-go-play-but-its-fading-fast/#11124d4b193d"&gt;Gen 2 Caused A Huge Spike In 'Pokémon GO' Play, But It's Fading Fast&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ซึ่งปัญหานี้จะหมดไปถ้าเราใช้บริการ Cloud&lt;/strong&gt; เนื่องจาก feature หลักของ Cloud Platform ไม่ว่าเจ้าไหนก็ตาม คือเราจะต้องสามารถ manage resource ต่าง ๆ ได้เอง ผ่าน online user interface เพราะฉะนั้น ไม่ว่าจะเป็นจำนวนเครื่อง server หรือ spec ของเครื่อง เราก็สามารถที่จะเพิ่มเมื่อจำเป็นต้องใช้มากขึ้น หรือลดลง หรือลบทิ้ง เมื่อไม่มีความจำเป็นต้องใช้แล้วได้ตลอดเวลา&lt;br&gt;&lt;br&gt;
อีกทั้งยังมี service ต่าง ๆ provide ให้ในหลาย ๆ ระดับ ตั้งแต่การเช่าเครื่องเปล่า (Infrastructure as a Service) ไปจนถึง deploy ในระดับ function เล็ก ๆ ที่ช่วยอำนวยความสะดวกให้เราส่งแค่โค้ดขึ้นไปรันได้เลย ไม่ต้อง manage infrastructure หรือ setup environment เอง (Function as a Service)&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gTF1N6Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bj29v5hy5b9lcasyazr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gTF1N6Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bj29v5hy5b9lcasyazr6.png" alt="compute-engine.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ภาพ User Interface ของ Google Cloud Platform สำหรับ manage VM instances&lt;/em&gt;&lt;br&gt;&lt;br&gt;
และ Cloud Providers ส่วนใหญ่ ไม่ว่าจะเป็น Google หรือ Amazon ก็มักจะมี quota ให้ทดลองใช้ฟรีกันทั้งสิ้น ซึ่งเหมาะอย่างมากกับการสร้าง prototype เล็ก ๆ เพื่อการศึกษา หรือ pitching&lt;br&gt;&lt;br&gt;
โดยสำหรับ Google Cloud Platform ที่เราจะพูดถึงกันในบทความนี้ มี quota ให้ใช้ฟรี $300 ต่อ account เป็นเวลา 12 เดือน และถึงจะใช้หมดแล้วก็ไม่ต้องกลัวว่าจะถูกหักเงินโดยไม่รู้ตัว เพราะ Google จะ stop service ต่าง ๆ ให้อัตโนมัติ หรือถ้าอยากจะเปลี่ยนไปใช้แบบเสียเงินหลักจากหมด quota แล้วก็สามารถ upgrade ได้ทันทีครับ&lt;br&gt;&lt;br&gt;
ต่อไปเราจะมาดูวิธีการสมัคร Google Cloud Platform ให้ได้เครดิต $300 กันนะครับ&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Get Started!&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ต่อไปเรามาดูสิ่งที่ต้องเตรียม และขั้นตอนการสมัครโดยละเอียด (ซึ่งอาจจะละเอียดเกินไป555)&lt;br&gt;
หรือถ้าหากต้องการอ่านสรุปสั้น ๆ สามารถข้ามไปที่ส่วน Summary ได้เลยครับ&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;สิ่งที่ต้องเตรียม...&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;gmail ใหม่ ๆ&lt;/strong&gt; 1 email ที่ยังไม่เคยเปิดใช้ Google Cloud Platform มาก่อน&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xycWYU2S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/maqidyvjlvirs60j5nl4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xycWYU2S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/maqidyvjlvirs60j5nl4.png" alt="new-gmail.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
ถ้ายังไม่มี หรือไม่แน่ใจว่าเคยใช้ Google Cloud มาก่อนหรือเปล่า ก็ไปสมัครใหม่ไว้เลยครับ &amp;gt;&amp;gt;&amp;gt; &lt;a href="https://mail.google.com/"&gt;GMail&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;บัตรเครดิต หรือบัตรเดบิตที่สามารถตัดเงินได้&lt;/strong&gt; &lt;em&gt;อันนี้ไม่ต้องตกใจนะครับ&lt;/em&gt; ตามหัวข้อของ blog นี้เลย &lt;strong&gt;เราจะไม่เสียเงินสักบาทแน่นอนครับ555&lt;/strong&gt; แค่ google ต้องการเลขบัตรเอาไว้เพื่อเช็คว่าเราเป็นคนจริง ๆ ไม่ใช่หุ่นยนต์ แล้วเผื่อหลังจากหมดช่วงฟรีแล้วเราอยากจะจ่ายเงินเพื่อใช้บริการต่อไปเลย ก็จะทำได้สะดวก ๆ เท่านั้นเองครับ&lt;br&gt;&lt;br&gt;
&lt;strong&gt;ย้ำอีกครั้ง เราจะไม่เสียตังสักบาท&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ถ้าพร้อมแล้ว ก็เริ่มกันเลย
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;เข้าไปที่ &lt;a href="https://console.cloud.google.com/"&gt;console.cloud.google.com&lt;/a&gt;&lt;/strong&gt; ก็จะเจอกับหน้าต้อนรับตามรูปด้านล่างนี้&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uwwiR7tL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hn375hofng5jbivjng4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uwwiR7tL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hn375hofng5jbivjng4b.png" alt="console-1.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
เช็คเครื่องหมายถูกที่ &lt;em&gt;I agree...&lt;/em&gt; แล้วก็กดปุ่ม &lt;em&gt;Continue&lt;/em&gt; ได้เลย&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ต่อไปคลิกที่ &lt;em&gt;TRY FOR FREE&lt;/em&gt;&lt;/strong&gt; เพื่อรับเครดิต &lt;strong&gt;$300&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UVZO_MTL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f95rqik5day5bno1j7bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UVZO_MTL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f95rqik5day5bno1j7bg.png" alt="activate-trial-crop.png"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;เลือกประเทศไทย, กด &lt;em&gt;I have read and agree...&lt;/em&gt; แล้วกด &lt;em&gt;Continue&lt;/em&gt; ต่อเลย&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p9kWWPNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nwbw1oujaed1u4vxeho5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p9kWWPNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nwbw1oujaed1u4vxeho5.png" alt="activate-step-1.png"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ตรงนี้ Google ก็จะถามข้อมูลส่วนตัวเราเล็กน้อย&lt;/strong&gt; &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q89qUJRD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/91cell0e13sh6wd8k41f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q89qUJRD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/91cell0e13sh6wd8k41f.png" alt="activate-step-2-1.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
ก็ให้กรอกข้อมูลลงไปให้ครบครับ &lt;strong&gt;account type&lt;/strong&gt; ให้เลือกเป็น &lt;em&gt;Individual&lt;/em&gt; แล้วด้านล่างก็กรอกที่อยู่ address, เมือง, จังหวัด ให้ครบ&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ต่อมาส่วนด้านล่าง Google ก็จะถามข้อมูลบัตรเครดิต หรือเดบิตของเรา&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fDfoNvvh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wknr8umk4r3e45snwea6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fDfoNvvh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wknr8umk4r3e45snwea6.png" alt="activate-step-2-2.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
อย่างที่บอกเมื่อข้างต้น กรอกไปได้เลยครับ การใช้งานทั้งหมดจะหักจากเครดิต $300 ก่อน แล้วถึงเครดิตจะหมด Services ต่าง ๆ ที่รันอยู่ ก็แค่จะหยุดทำงานไปให้เองครับ&lt;br&gt;&lt;br&gt;
อันนี้ไม่ต้องเชื่อผมก็ได้ ลองอ่านเงื่อนไขเองได้ที่กรอบด้านขวาเลยครับ แต่ก็จะเหมือนกับที่ผมบอกแหละ เพราะผมก็ก๊อปมาจากตรงนั้น555&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KrNhAs5H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ltk44ldfgkrei34qwh28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KrNhAs5H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ltk44ldfgkrei34qwh28.png" alt="terms.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Google จะไม่หักเงินเราโดยพลการแน่นอน &lt;strong&gt;ยกเว้นว่าเราเผลอไปกดยินยอมให้หักเงินได้&lt;/strong&gt; นะครับ&lt;br&gt;&lt;br&gt;
กรอกเสร็จแล้วก็คลิก &lt;em&gt;START MY FREE TRIAL&lt;/em&gt; ได้เลยครับ&lt;br&gt;&lt;br&gt;
ถ้าหลังจากที่ทำตามแล้วพบว่าบัตรเครดิต หรือเดบิตมีการตัดเงินจาก Google ก็ไม่ต้องตกใจนะครับ บางที Google มันจะอยากยืนยันว่าบัตรที่เรากรอกมาเป็นบัตรที่ใช้งานได้จริงหรือเปล่า เลยลองตัดเงินดูเล่น ๆ แต่สักพักมันก็จะคืนเงินให้เท่าเดิมครับ&lt;br&gt;&lt;br&gt;
&lt;em&gt;&lt;strong&gt;ระวัง1:&lt;/strong&gt; ระวังว่าถ้าบัตรมี&lt;strong&gt;เงินน้อยเกินไป&lt;/strong&gt; อาจจะไม่พอให้ Google ตัด แล้วมันก็จะยังไม่อนุญาตให้เราใช้ account นั้น วิธีแก้คือก็ใส่เงินเอาไว้สักหน่อยให้มันตัดได้ แล้วไปกรอกข้อมูลบัตรใหม่อีกทีครับ&lt;br&gt;&lt;br&gt;
&lt;strong&gt;ระวัง2:&lt;/strong&gt; มีกรณีที่วงเงินของบัตรที่สามารถตัดได้นั้นไม่พอ ให้โทรติดต่อไปที่ธนาคารของบัตรที่เราใช้ แล้วขอขยายวงเงินได้ครับ&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;เสร็จแล้วก็จะขึ้น message welcome to GCP ขึ้นมา&lt;/strong&gt; มันก็จะย้ำอีกครั้ง ว่า&lt;em&gt;เราจะไม่ชาร์จคุณนะ ยกเว้นว่าคุณเปิด automatic billing&lt;/em&gt; - ถ้าใครอยากเปิด billing เลยก็คลิกเข้าไปตาม link ได้เลยครับ หรือถ้าไม่ก็กดข้ามเลย&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SZfOSRWk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ny5lnh8a7w6cufl6mbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SZfOSRWk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ny5lnh8a7w6cufl6mbd.png" alt="gcp-welcome.png"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
พอเสร็จขั้นตอนทั้งหมดแล้วด้านซ้ายมือก็จะมี services ต่าง ๆ ให้ได้ลองใช้กันได้ฟรี ๆ ในงบ $300 ภายในเวลา 12 เดือน&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u_ge2lJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aqvn4ecgt0f3r2cen943.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u_ge2lJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aqvn4ecgt0f3r2cen943.png" alt="services.png"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud:&lt;/strong&gt; provides บริการต่าง ๆ ให้ สามารถสร้าง projects, deploy applications, หรือใช้งาน services ได้ทั้งแบบ manage เอง และ serverless &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; &lt;em&gt;Google Cloud Account&lt;/em&gt; และ &lt;em&gt;free credit &lt;strong&gt;$300&lt;/strong&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;สิ่งที่ต้องเตรียม:&lt;/strong&gt; &lt;em&gt;gmail ใหม่&lt;/em&gt; และ&lt;em&gt;บัตรเครดิต หรือเดบิตที่สามารถตัดเงินได้&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;วิธีทำ:&lt;/strong&gt; เข้า &lt;a href="https://console.cloud.google.com/"&gt;console.cloud.google.com&lt;/a&gt; คลิก &lt;em&gt;TRY FOR FREE&lt;/em&gt; แล้วก็กรอกข้อมูลไปเรื่อย ๆ &lt;strong&gt;(แค่นี้จริง ๆ 555)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ข้อควรระวัง:&lt;/strong&gt; $300 credits จะมีระยะเวลาแค่ 12 เดือนเท่านั้น และถ้าไม่อยากเสียเงินหลังจากนั้น อย่าเผลอไปเปิด auto billing&lt;/li&gt;
&lt;/ul&gt;



&lt;center&gt;&lt;strong&gt;- ขอบคุณที่อ่านครับ -&lt;/strong&gt;&lt;/center&gt;

</description>
      <category>googlecloud</category>
      <category>cloud</category>
      <category>free</category>
      <category>gcp</category>
    </item>
  </channel>
</rss>
