บทความ

บทนำ: คุณจะได้อะไร และคู่มือนี้เหมาะกับใคร

Yandex Maps กลายเป็นมากกว่าแอปนำทาง มันคือแคตตาล็อกธุรกิจที่ครบที่สุดในรัสเซีย การ์ดองค์กรเก็บสิ่งที่นักการตลาด นัก Arbitrage หรือเจ้าของธุรกิจต้องการ: ที่อยู่แม่นยำ เบอร์โทร เว็บไซต์ เวลาทำการ คะแนน และรีวิวจากลูกค้านับพัน ปัญหาคือการไล่ดูการ์ด 500 ใบของคลินิกทันตกรรมในเมืองเดียวด้วยมือเป็นไปไม่ได้ และบริการสำเร็จรูปก็แพงหรือให้ข้อมูลเก่า

ในคู่มือนี้ เราจะเขียน Parser Yandex Maps ด้วย Python ด้วยกัน ไม่มีโปรแกรมเสียเงิน ไม่มีเซิร์ฟเวอร์คนอื่น เมื่อจบคู่มือคุณจะมี:

  • สคริปต์ที่ใช้งานได้ เก็บการ์ดองค์กรตามคำค้นและเมืองที่ต้องการ
  • ตาราง Excel ที่มีชื่อ หมวดหมู่ ที่อยู่ เบอร์โทร เว็บไซต์ คะแนน และเวลาทำการ
  • ตารางแยกรีวิว: ผู้เขียน คะแนน วันที่ ข้อความ
  • ความเข้าใจวิธีทำงานอย่างระมัดระวัง ไม่เจอแคปช่าและไม่โดนแบน โดยกระจายโหลดผ่านมือถือ โปรกซี่

ขอบเขตสำคัญของบทความนี้: เราจะพูดถึงการ์ดองค์กรใน Maps เท่านั้น การแยกผลการค้นหาปกติของ Yandex การใช้เบราว์เซอร์ต้านการตรวจจับ และการตั้งค่ามือถือ โปรกซี่พื้นฐานมีอธิบายในบทความอื่นของบล็อก เราไม่อธิบายซ้ำ แต่จะอ้างอิงเท่านั้น

คู่มือนี้เหมาะกับใคร

  • นักการตลาดและเจ้าของธุรกิจ ที่ต้องการฐานคู่แข่งหรือพาร์ทเนอร์ในภูมิภาคพร้อมเบอร์โทรและคะแนนจริง
  • นัก Arbitrage ที่เก็บ niches ออฟไลน์และข้อเสนอท้องถิ่นเพื่อวิเคราะห์ต่อ
  • นักพัฒนา ที่ได้รับมอบหมายให้เก็บข้อมูลจาก Maps และไม่อยากเริ่มจากศูนย์
  • นักวิเคราะห์ ที่ศึกษารีวิวเพื่อประเมินคุณภาพบริการใน niche

สิ่งที่ต้องรู้ก่อน

ไม่มีอะไรยาก แค่ติดตั้งโปรแกรมได้ เปิด command line เป็น และคัดลอกข้อความได้ ไม่จำเป็นต้องมีประสบการณ์เขียนโปรแกรม เพราะโค้ดพร้อมหมดแล้ว คุณแค่ใส่ค่าของตัวเอง ถ้าคุณเคยรันสคริปต์ Python มาก่อน จะง่ายมาก ในส่วนสำหรับขั้นสูง เราจะพูดถึง async และการจัดการ response เครือข่าย แต่ข้ามได้

ใช้เวลานานแค่ไหน

  • เตรียม environment: 30-40 นาที
  • เขียนและดีบัก parser ทีละขั้น: 1.5-2 ชั่วโมง
  • เก็บจริงครั้งแรก 300-500 การ์ด: 1-3 ชั่วโมงแบบรันเบื้องหลัง ระหว่างทำอย่างอื่น

รวมแล้วครึ่งวันทำงาน คุณจะได้เครื่องมือที่จะประหยัดเวลาทำมือเป็นสัปดาห์

การเตรียมตัวล่วงหน้า: เครื่องมือ สิทธิ์ และข้อกำหนด

ก่อนเขียนโค้ด มารวมทุกอย่างที่ต้องใช้ ข้ามส่วนนี้ถ้าคุณติดตั้ง Python แล้วและมีมือถือ โปรกซี่

ข้อกำหนดระบบ

  • Windows 10 หรือ 11, macOS 12 ขึ้นไป หรือ Linux ทันสมัยใดก็ได้
  • RAM ขั้นต่ำ 8 GB เพราะเราจะรันเบราว์เซอร์ Chromium จริง
  • พื้นที่ว่าง 3-4 GB สำหรับ Python เบราว์เซอร์ และผลลัพธ์
  • อินเทอร์เน็ตเสถียร ความเร็วไม่สำคัญ ขาดการตัดการเชื่อมต่อสำคัญกว่า

สิ่งที่ต้องติดตั้ง

  1. Python 3.11 หรือ 3.12 ดาวน์โหลดตัวติดตั้งจากเว็บไซต์ทางการ python.org ตอนติดตั้งบน Windows ต้องติ๊ก Add Python to PATH ที่ด้านล่างหน้าต่างแรก ไม่งั้นคำสั่งใน console จะไม่ทำงาน
  2. โปรแกรมแก้ไขโค้ด ใช้ Visual Studio Code, PyCharm Community หรือแม้แต่ Notepad++ ก็ได้ เราจะอ้างอิง VS Code แต่ไม่ต่างกันสำหรับงานเรา
  3. ไลบรารี Python: playwright สำหรับควบคุมเบราว์เซอร์, pandas และ openpyxl สำหรับตาราง, requests สำหรับตรวจสอบโปรกซี่ จะติดตั้งในขั้นแรก
  4. เบราว์เซอร์ Chromium สำหรับ Playwright ดาวน์โหลดด้วยคำสั่งแยก ขนาดประมาณ 150 MB

เข้าถึงมือถือ โปรกซี่

นี่คือหัวใจของส่วน "ไม่โดนแบน" Yandex Maps ไวต่อความถี่คำขอจากที่อยู่เดียว มือถือ โปรกซี่ให้ IP ของผู้ให้บริการมือถือจริงที่มีผู้ใช้ทั่วไปหลายพันคนใช้พร้อมกัน Yandex จึงไว้วางใจมาก ในหน้าสมาชิก mobileproxy.space หลังซื้อโปรกซี่ คุณต้องใช้สี่ค่า:

  • โฮสต์ (ที่อยู่เซิร์ฟเวอร์) และ พอร์ต สำหรับเชื่อมต่อ HTTP
  • ชื่อผู้ใช้ และ รหัสผ่าน สำหรับยืนยันตัวตน
  • ลิงก์เปลี่ยน IP: URL พิเศษที่เมื่อเรียกแล้วโปรกซี่จะได้ที่อยู่ใหม่จากผู้ให้บริการ คัดลอกไปเก็บในไฟล์แยก จะใช้ในขั้นที่ห้า

คำแนะนำ: เลือกโปรกซี่ในภูมิภาคเดียวกันหรืออย่างน้อยประเทศเดียวกับเมืองที่แยก Maps ปรับผลลัพธ์ตาม geolocation และโปรกซี่จากประเทศอื่นอาจแสดงรายการองค์กรไม่ครบหรืออินเทอร์เฟซภาษาอื่น

สำรองข้อมูลและโฟลเดอร์ทำงาน

สร้างโฟลเดอร์บนดิสก์ เช่น maps_parser ข้างในจะมีสคริปต์และผลลัพธ์ เราจะบันทึกข้อมูลกลางลงไฟล์หลังทุกการ์ด ดังนั้นต่อให้สคริปต์พังที่องค์กรที่ 300 องค์กร 299 แรกจะไม่หาย ควรสำรองไฟล์ผลลัพธ์ก่อนรันซ้ำทุกครั้ง เพียงเปลี่ยนชื่อไฟล์เก่าเติมวันที่

ตรวจสอบ: เปิด command line (บน Windows กด Win+R พิมพ์ cmd แล้ว Enter) แล้วพิมพ์ python --version ถ้าเห็นบรรทัดแบบ Python 3.12.x แสดงว่าเตรียมเสร็จ ถ้าเป็น error ให้ติดตั้ง Python ใหม่พร้อมติ๊ก Add to PATH

แนวคิดพื้นฐาน: Maps ทำงานอย่างไร และการแยกการ์ดคืออะไร

ก่อนเขียนโค้ด มาทำความเข้าใจคำศัพท์ ง่ายมาก แต่ถ้าไม่มีจะไม่เข้าใจว่าทำไมเราทำแบบนี้

คำศัพท์สำคัญอธิบายง่าย ๆ

  • Parsing (Scraping) — การเก็บข้อมูลอัตโนมัติจากหน้าเว็บ โปรแกรมเปิดหน้าเว็บเหมือนคน แล้วดึงส่วนที่ต้องการออกมา
  • การ์ดองค์กร — หน้าของ Maps ที่มีที่อยู่แบบ yandex.ru/maps/org/ชื่อ/รหัสตัวเลข/ ที่นี่มีเบอร์โทร ที่อยู่ รีวิว และข้อมูลอื่น รายการองค์กรด้านซ้ายเป็นแค่หน้าร้านที่เราเอาแค่ลิงก์ไปยังการ์ด
  • Selector — "ที่อยู่" ของ element ในหน้า เช่น class business-contacts-view__address ชี้ไปที่บล็อกที่อยู่ สคริปต์ใช้ selector หาข้อความ
  • Headless browser — เบราว์เซอร์จริงที่โปรแกรมควบคุม อาจมีหน้าต่าง (เห็นว่ากำลังทำอะไร) หรือไม่มีก็ได้
  • Playwright — ไลบรารีควบคุมเบราว์เซอร์จาก Python เราเลือกเพราะ Maps สร้างด้วย JavaScript ทั้งหมด และคำขอ HTML ธรรมดาจะได้หน้าเปล่าไม่มีข้อมูล
  • มือถือ โปรกซี่ — ตัวกลางระหว่างคอมกับเว็บที่มี IP เครือข่ายมือถือ เว็บเห็นที่อยู่ผู้ให้บริการ ไม่ใช่ของคุณ
  • การหมุน IP — เปลี่ยนที่อยู่โปรกซี่เป็นระยะ เพื่อไม่ให้โหลดกระจุกที่ IP เดียว
  • แคปช่า — หน้าเช็ค "ยืนยันว่าคุณไม่ใช่หุ่นยนต์" สำหรับเราคือสัญญาณว่าเร็วเกินไป เราไม่แก้ด้วยบริการภายนอก แค่หยุด เปลี่ยน IP และลดความเร็ว

หลักการทำงานของ Parser Yandex Maps

ตรรกะง่ายมาก สามเฟส ก่อนอื่นเราได้รายการลิงก์ไปยังการ์ดองค์กรที่ต้องการ จากนั้นเปิดแต่ละการ์ดทีละใบและดึงข้อมูล สุดท้ายรวมทุกอย่างลงตาราง ระหว่างนั้นเราหยุดพักแบบสุ่มเวลา และเปลี่ยน IP เป็นระยะผ่านลิงก์เปลี่ยนที่อยู่

สิ่งที่ต้องเข้าใจเรื่องกฎหมายและจริยธรรม

คำเตือน: เก็บเฉพาะข้อมูลสาธารณะขององค์กรและใช้เพื่อวิเคราะห์ เบอร์โทรและที่อยู่บริษัทไม่ใช่ข้อมูลส่วนบุคคล แต่ชื่อผู้เขียนรีวิวอาจถือเป็นข้อมูลส่วนบุคคลตามกฎหมาย 152-ФЗ อย่าเก็บโดยไม่จำเป็นและอย่าใช้เบอร์ที่เก็บไปส่งข้อความจำนวนมากโดยไม่ได้รับความยินยอม เพราะผิดกฎหมายโฆษณาและกฎของ Yandex จำไว้ว่าข้อตกลงผู้ใช้ของ Yandex จำกัดการเก็บอัตโนมัติ ดังนั้นให้โหลดน้อยที่สุด และสำหรับโปรเจกต์เชิงพาณิชย์ขนาดใหญ่ควรพิจารณา API Yandex อย่างเป็นทางการสำหรับค้นหาองค์กร

ขั้นที่ 1: ตั้งค่า environment และเชื่อมต่อมือถือ โปรกซี่

เป้าหมาย: ติดตั้งไลบรารีทั้งหมด ดาวน์โหลดเบราว์เซอร์ และยืนยันว่าคำขอผ่านมือถือ โปรกซี่ ไม่ใช่ตรง

ติดตั้งไลบรารี

  1. เปิด command line หรือ terminal
  2. ไปที่โฟลเดอร์ทำงานด้วยคำสั่ง cd และ path เช่น cd C:\maps_parser บน Windows หรือ cd ~/maps_parser บน macOS และ Linux
  3. สร้าง virtual environment เพื่อไม่ให้ไลบรารีรบกวนโปรเจกต์อื่น: python -m venv venv
  4. เปิดใช้งาน Windows: venv\Scripts\activate macOS และ Linux: source venv/bin/activate จะมี (venv) ต้นบรรทัด terminal
  5. ติดตั้งไลบรารีด้วยคำสั่งเดียว:
pip install playwright pandas openpyxl requests
playwright install chromium

คำสั่งที่สองจะดาวน์โหลด Chromium ใช้เวลา 2-5 นาทีตามความเร็วอินเทอร์เน็ต รอจน terminal กลับมาแสดง prompt

ตรวจสอบโปรกซี่

สร้างไฟล์ check_proxy.py ในโปรแกรมแก้ไข แล้ววางโค้ด แทน ЛОГИН, ПАРОЛЬ, ХОСТ และ ПОРТ ด้วยค่าจากหน้าสมาชิก:

import requests

proxy = 'http://ЛОГИН:ПАРОЛЬ@ХОСТ:ПОРТ'
proxies = {'http': proxy, 'https': proxy}

direct = requests.get('https://api.ipify.org?format=json', timeout=30).json()
via_proxy = requests.get('https://api.ipify.org?format=json', proxies=proxies, timeout=30).json()
print('Ваш прямой IP:', direct['ip'])
print('IP через прокси:', via_proxy['ip'])

รันไฟล์ด้วยคำสั่ง python check_proxy.py คุณควรเห็นสองที่อยู่ต่างกัน ถ้าซ้ำกันหรือ error เชื่อมต่อ แสดงว่าโปรกซี่ไม่ทำงาน ไปต่อไม่ได้

ตรวจสอบการเปลี่ยน IP

เปิดลิงก์เปลี่ยน IP จากหน้าสมาชิกในเบราว์เซอร์ปกติ ปกติจะตอบสั้น ๆ ว่าสำเร็จ รอ 15-30 วินาที แล้วรัน check_proxy.py อีกครั้ง ที่อยู่ผ่านโปรกซี่ควรเปลี่ยน จำไว้ว่าใช้เวลากี่วินาทีจริง เราจะใส่ในสคริปต์ขั้นที่ห้า

คำแนะนำ: บันทึกการตั้งค่าโปรกซี่ทั้งหมดในไฟล์ config.py ด้วยตัวแปร PROXY_HOST, PROXY_PORT, PROXY_LOGIN, PROXY_PASSWORD และ CHANGE_IP_URL จากนั้นในสคริปต์อื่นเขียน from config import * ก็พอ ไม่ต้องพิมพ์รหัสซ้ำสิบรอบ

ตรวจสอบ: คำสั่ง playwright --version แสดงเลขเวอร์ชัน check_proxy.py แสดง IP ผู้ให้บริการมือถือที่ต่างจากบ้าน และหลังเรียกเปลี่ยนที่อยู่ ที่อยู่เปลี่ยน

ปัญหาที่อาจเจอ

  • Error "pip ไม่ใช่คำสั่งภายใน" Python ติดตั้งโดยไม่เพิ่มใน PATH ติดตั้งใหม่พร้อมติ๊ก หรือใช้คำสั่ง py -m pip แทน pip
  • Error 407 Proxy Authentication Required ชื่อผู้ใช้หรือรหัสผ่านผิด ตรวจว่ามีช่องว่างเกินตอนคัดลอกไหม
  • Timeout เมื่อขอผ่านโปรกซี่ พอร์ตผิด หรือโปรกซี่กำลังเปลี่ยน IP รอครึ่งนาทีแล้วลองใหม่

ขั้นที่ 2: เก็บลิงก์ไปยังการ์ดองค์กร

เป้าหมาย: ได้ไฟล์ links.json ที่มีรายการที่อยู่การ์ดขององค์กรทั้งหมดตามคำค้นและเมือง

ที่นี่เราแค่แอบดูรายการผลลัพธ์ Maps ครั้งเดียวเพื่อดึงลิงก์ ข้อมูลจริงเราจะเอาเฉพาะจากการ์ด ดังนั้นรายการเป็นแค่สารบัญ ไม่มากไปกว่านั้น

รายการองค์กรทำงานอย่างไร

เมื่อคุณพิมพ์คำค้นเช่น "คลินิกทันตกรรม Казань" ใน Maps จะมีแผงเลื่อนด้านซ้ายที่มี snippet แต่ละ snippet มีลิงก์ไปยังการ์ด รายการโหลดเป็นชุดเมื่อเลื่อน ดังนั้นสคริปต์จะหมุนล้อเมาส์บนแผงและเก็บลิงก์หลังทุกการเลื่อน จนไม่มีลิงก์ใหม่

เขียนสคริปต์เก็บลิงก์

สร้างไฟล์ collect_links.py:

from playwright.sync_api import sync_playwright
import time, random, json
from config import *

PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
QUERY = 'стоматология Казань'
MAX_SCROLLS = 40

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, proxy=PROXY)
    context = browser.new_context(locale='ru-RU', viewport={'width': 1400, 'height': 900})
    page = context.new_page()
    page.goto('https://yandex.ru/maps/?text=' + QUERY, wait_until='domcontentloaded')
    time.sleep(random.uniform(5, 8))
    page.mouse.move(350, 500)
    links = set()
    stale = 0
    for i in range(MAX_SCROLLS):
        before = len(links)
        page.mouse.wheel(0, random.randint(1200, 2000))
        time.sleep(random.uniform(1.5, 3.5))
        for a in page.query_selector_all('a[href*="/maps/org/"]'):
            href = a.get_attribute('href') or ''
            clean = href.split('?')[0]
            if clean.startswith('/'):
                clean = 'https://yandex.ru' + clean
            links.add(clean)
        stale = stale + 1 if len(links) == before else 0
        print(f'Прокрутка {i+1}: ссылок {len(links)}')
        if stale >= 4:
            break
    with open('links.json', 'w', encoding='utf-8') as f:
        json.dump(sorted(links), f, ensure_ascii=False, indent=2)
    print('Итого собрано:', len(links))
    browser.close()

อธิบายว่าเกิดอะไรขึ้น

  1. บรรทัด headless=False เปิดเบราว์เซอร์มีหน้าต่าง ตอนดีบักต้องมี คุณจะเห็นว่าการ์ดโหลดไหมและมีแคปช่าไหม
  2. page.mouse.move(350, 500) วางเคอร์เซอร์บนแผงซ้าย ถ้าไม่มี ล้อเมาส์จะเลื่อนแผนที่ ไม่ใช่รายการ
  3. Selector a[href*="/maps/org/"] หาลิงก์ทั้งหมดที่มี /maps/org/ ทนกว่า class ที่ Yandex เปลี่ยนทุกไม่กี่เดือน
  4. ตัวนับ stale หยุดลูปถ้า 4 การเลื่อนติดกันไม่ลิงก์ใหม่ แสดงว่ารายการจบ
  5. ลิงก์ถูกล้างพารามิเตอร์หลังเครื่องหมาย ? เพื่อไม่ให้องค์กรเดียวซ้ำในไฟล์

รันสคริปต์: python collect_links.py จะเปิดหน้าต่างเบราว์เซอร์ แผนที่โหลด แผงเริ่มเลื่อน ใน terminal จะมีตัวนับลิงก์วิ่ง สำหรับเมืองกลาง ๆ ต่อคำค้นมักได้ 100-400 การ์ดใน 2-4 นาที

คำแนะนำ: Maps มักให้ไม่เกิน 500 ผลลัพธ์ต่อคำค้น ถ้าต้องการทั้ง niche ในเมืองใหญ่ แตกคำค้นตามเขต: "стоматология Вахитовский район Казань", "стоматология Советский район Казань" รวมลิงก์จากคำค้นต่าง ๆ ผ่าน set เหมือนในโค้ดด้านบน ตัวซ้ำจะหายเอง

ตรวจสอบ: มีไฟล์ links.json ในโฟลเดอร์ ข้างในเป็นที่อยู่แบบ https://yandex.ru/maps/org/ชื่อ/1234567890/ เปิดสองสามที่อยู่ในเบราว์เซอร์ปกติและยืนยันว่าเป็นการ์ดองค์กรที่ต้องการ

ปัญหาที่อาจเจอ

  • เก็บได้ศูนย์ลิงก์ น่าจะหน้าโหลดไม่ทันหรือเคอร์เซอร์ไม่อยู่บนรายการ เพิ่มหยุดแรกเป็น 10 วินาทีและตรวจพิกัดเมาส์ แผงต้องอยู่ใต้เคอร์เซอร์
  • รายการไม่เลื่อน แผนที่เลื่อน ลดหรือเพิ่มพิกัด X ใน mouse.move ตามความกว้างแผงในหน้าต่างคุณ
  • เจอแคปช่าทันที เปลี่ยน IP ผ่านลิงก์ รอหนึ่งนาทีแล้วรันใหม่ ถ้าซ้ำ ลองช่องทางโปรกซี่อื่น

ขั้นที่ 3: แยกการ์ดองค์กร — ชื่อ ที่อยู่ เบอร์โทร เว็บไซต์

เป้าหมาย: เขียนฟังก์ชันที่เปิดการ์ดเดียวและคืน dictionary ข้อมูลองค์กรหลัก แล้วรันกับทุก link ใน links.json

วิธีหา selector เอง

Yandex เปลี่ยนชื่อ class ในโค้ดเป็นระยะ ดังนั้นสำคัญที่ต้องหาด้วยตัวเอง ไม่พึ่งโค้ดสำเร็จ วิธีทำ:

  1. เปิดการ์ดองค์กรใดก็ได้ใน Chrome ปกติ
  2. คลิกขวาที่เบอร์โทรองค์กร เลือก ตรวจสอบโค้ด (หรือกด F12 แล้วคลิกที่ element ด้วยเครื่องมือเลือก)
  3. ในแผงที่เปิดจะเห็น tag ไฮไลต์พร้อม attribute class เช่น class="card-phones-view__phone-number" นี่คือ selector ในโค้ดเขียนด้วยจุดนำหน้า
  4. ทำซ้ำสำหรับชื่อ ที่อยู่ เว็บไซต์ คะแนน และเวลาทำการ จด class ลงโน้ต

Class อาจมีหลายคำคั่นด้วยช่องว่าง เอาแรกที่สุด "สื่อความ" ที่มีขีดล่างคู่ข้างใน ตอนเขียนคู่มือ selector ในโค้ดด้านล่างใช้ได้ แต่ตรวจก่อนรัน

เขียนฟังก์ชันแยกการ์ด

สร้างไฟล์ parse_cards.py:

from playwright.sync_api import sync_playwright
import time, random, json, csv, os
from config import *

PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
OUT = 'orgs.csv'
FIELDS = ['url', 'name', 'category', 'address', 'phones', 'site', 'rating', 'reviews_count', 'hours']

def grab(page, selector):
    el = page.query_selector(selector)
    return el.inner_text().strip() if el else ''

def parse_card(page, url):
    page.goto(url, wait_until='domcontentloaded')
    time.sleep(random.uniform(3, 6))
    more = page.query_selector('.card-phones-view__more')
    if more:
        more.click()
        time.sleep(random.uniform(1, 2))
    phones = [e.inner_text().strip() for e in page.query_selector_all('.card-phones-view__phone-number')]
    return {
        'url': url,
        'name': grab(page, 'h1.orgpage-header-view__header'),
        'category': grab(page, '.orgpage-categories-info-view'),
        'address': grab(page, '.business-contacts-view__address'),
        'phones': '; '.join(dict.fromkeys(phones)),
        'site': grab(page, '.business-urls-view__text'),
        'rating': grab(page, '.business-rating-badge-view__rating-text'),
        'reviews_count': grab(page, '.business-header-rating-view__text'),
        'hours': grab(page, '.business-working-status-view'),
    }

def load_done():
    if not os.path.exists(OUT):
        return set()
    with open(OUT, encoding='utf-8') as f:
        return {row['url'] for row in csv.DictReader(f)}

links = json.load(open('links.json', encoding='utf-8'))
done = load_done()
todo = [u for u in links if u not in done]
print(f'Всего {len(links)}, осталось {len(todo)}')

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, proxy=PROXY)
    page = browser.new_context(locale='ru-RU').new_page()
    new_file = not os.path.exists(OUT)
    with open(OUT, 'a', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            writer.writeheader()
        for n, url in enumerate(todo, 1):
            try:
                row = parse_card(page, url)
                writer.writerow(row)
                f.flush()
                print(f'{n}/{len(todo)} {row["name"]} | {row["phones"]}')
            except Exception as e:
                print('Ошибка на', url, e)
            time.sleep(random.uniform(4, 9))
    browser.close()

สิ่งที่สำคัญตรงนี้

  1. ปุ่ม "แสดงเบอร์โทร" บางการ์ดซ่อนเบอร์เต็มหลังปุ่ม เราหาด้วย class card-phones-view__more แล้วคลิกถ้าเจอ หลังจากนั้นเก็บเบอร์
  2. บันทึกหลังทุกการ์ด ฟังก์ชัน f.flush() บังคับเขียนข้อมูลลงดิสก์ ถ้าสคริปต์พัง ทุกอย่างที่เก็บจะอยู่ใน orgs.csv
  3. ทำงานต่อได้ ฟังก์ชัน load_done อ่านลิงก์ที่เก็บแล้ว และเมื่อรันซ้ำ สคริปต์ทำต่อจากที่หยุด ไม่เริ่มใหม่
  4. หยุด 4-9 วินาที ระหว่างการ์ด นี่คือจังหวะคนอ่านข้อมูลจริง ๆ อย่าลดในการรันแรก

รัน python parse_cards.py และดูห้าถึงสิบการ์ดแรกในหน้าต่างเบราว์เซอร์ คุณควรเห็นหน้าเปิด เบอร์โทร раскрывается ถ้าจำเป็น และใน terminal มีชื่อกับเบอร์

คำเตือน: ถ้าห้าการ์ดติดกันคืนชื่อว่าง หยุดสคริปต์ทันทีด้วย Ctrl+C เกือบแน่นอน Yandex เปลี่ยนโค้ด และ selector เก่า หาใหม่ตามคำแนะนำด้านบนและอัปเดตโค้ด ทำต่อกับบรรทัดว่างไม่มีความหมายและเพิ่มโหลดโปรกซี่เท่านั้น

คำแนะนำ: นอกจากข้อความ ควรบันทึกรหัสองค์กรด้วย: ส่วนตัวเลขสุดท้ายของ URL ใช้ง่ายในการเทียบฐานระหว่างการเก็บและติดตามบริษัทที่ปิด เพิ่ม field 'org_id': url.rstrip('/').split('/')[-1] ใน dictionary

ตรวจสอบ: ไฟล์ orgs.csv เปิดใน Excel มีคอลัมน์ name, address, phones ไม่น้อยกว่า 90 เปอร์เซ็นต์ของแถว เบอร์แสดงแบบ +7 (843) 000-00-00 คะแนนเป็นตัวเลขมีจุลภาค เช่น 4,7

ปัญหาที่อาจเจอ

  • เบอร์ว่าง แม้เว็บมี ปุ่ม "แสดงเบอร์โทร" มี class อื่น หาผ่าน F12 และแทนในโค้ด
  • ภาษารัสเซียใน CSV แสดงเป็นตัวอักษรประหลาด Excel ไม่รู้จัก encoding เปิดไฟล์ผ่านเมนู Данные, Из текста/CSV และเลือก UTF-8 หรือรอขั้นที่ 6 ที่เราแปลงเป็น xlsx
  • สคริปต์ค้างที่การ์ดหนึ่ง เพิ่มพารามิเตอร์ timeout=45000 ใน page.goto เพื่อให้เกิด exception หลัง 45 วินาทีและลูปไปต่อ

ขั้นที่ 4: เก็บรีวิวจากการ์ด

เป้าหมาย: สำหรับแต่ละองค์กรได้รายการรีวิวพร้อมคะแนน วันที่ และข้อความ และบันทึกแยกในไฟล์ reviews.csv

รีวิวอยู่ที่ไหน

ทุกการ์ดมีแท็บ "รีวิว" ที่อยู่แบบ https://yandex.ru/maps/org/ชื่อ/รหัส/reviews/ รีวิวโหลดเป็นชุดเมื่อเลื่อน เหมือนรายการองค์กรในขั้นที่สอง โดยค่าเริ่มต้นเรียงตามความเกี่ยวข้อง และสามารถเปลี่ยนเรียงเป็น "ตามใหม่"

เขียนฟังก์ชันเก็บรีวิว

สร้างไฟล์ parse_reviews.py พื้นฐานเหมือนขั้นก่อน เราจะแสดงแค่ฟังก์ชันและลูป:

def parse_reviews(page, url, max_scrolls=15):
    page.goto(url.rstrip('/') + '/reviews/', wait_until='domcontentloaded')
    time.sleep(random.uniform(3, 6))
    page.mouse.move(350, 600)
    seen = 0
    for _ in range(max_scrolls):
        page.mouse.wheel(0, random.randint(1500, 2500))
        time.sleep(random.uniform(1.5, 3))
        cards = page.query_selector_all('.business-review-view')
        if len(cards) == seen:
            break
        seen = len(cards)
    result = []
    for c in page.query_selector_all('.business-review-view'):
        def sub(sel):
            el = c.query_selector(sel)
            return el.inner_text().strip() if el else ''
        stars = c.query_selector_all('.business-rating-badge-view__star._full')
        result.append({
            'org_url': url,
            'author': sub('.business-review-view__author-name'),
            'date': sub('.business-review-view__date'),
            'stars': len(stars),
            'text': sub('.business-review-view__body-text'),
        })
    return result

ในลูปตามลิงก์ เรียก parse_reviews แทน parse_card และบันทึกแต่ละ element แยกบรรทัดใน reviews.csv ด้วย field org_url, author, date, stars, text ตรรกะทำงานต่อและการบันทึกหลังทุกองค์กรเหมือนเดิม

อธิบายรายละเอียด

  1. จำกัด max_scrolls=15 ร้านดังมีรีวิวสองสามพัน เก็บทั้งหมดไม่ค่อยจำเป็นและกินเวลาและคำขอมาก สิบห้าการเลื่อนมักพอสำหรับ 100-150 รีวิวล่าสุด
  2. คะแนนผ่านดาว คะแนนในรีวิวไม่ได้เขียนเป็นข้อความ แต่วาดเป็นดาว เรานับ element ที่มี modifier _full คือดาวที่ระบายสี
  3. รีวิวยาว บางข้อความซ่อนและต้องกด "เพิ่มเติม" ถ้าต้องการข้อความเต็ม ก่อนเก็บให้คลิกปุ่มทั้งหมดที่มี class business-review-view__expand ในการ์ด

คำเตือน: ชื่อผู้เขียนรีวิวเป็นข้อมูลผู้ใช้ ไม่ใช่องค์กร ถ้างานของคุณคือวิเคราะห์อารมณ์หรือหาข้อร้องเรียนทั่วไป field author ไม่จำเป็น อย่าเก็บโดยไม่มีเป้าหมาย เพื่อลดปัญหาตามกฎหมาย 152-ФЗ ถ้าจำเป็น ให้เก็บในเครื่องและไม่ส่งต่อบุคคลที่สาม

คำแนะนำ: เก็บรีวิวไม่ใช่ทุกองค์กร แต่ตามรายการที่กรองแล้ว เช่น เฉพาะคะแนนต่ำกว่า 4.0 หรือเฉพาะคู่แข่งตรง จะลดจำนวนคำขอหลายเท่าโดยไม่เสียคุณค่าข้อมูล

ตรวจสอบ: ใน reviews.csv องค์กรละ 20-150 แถว คอลัมน์ stars มีตัวเลข 1-5 ใน text มีข้อความภาษารัสเซียที่มีความหมาย วันที่แบบ "15 มกราคม" หรือ "3 มีนาคม 2026"

ปัญหาที่อาจเจอ

  • เจอศูนย์รีวิว ตรวจว่า URL ลงท้าย /reviews/ และแผงรีวิวอยู่ใต้เคอร์เซอร์ตอนเลื่อน
  • stars เป็น 0 ทุกอัน class ดาวที่ระบายเปลี่ยน หาผ่าน F12 โดยคลิกที่ดาว
  • รีวิวซ้ำ แผงเลื่อนไม่เต็มและหนึ่งบล็อกนับสองครั้ง ลบตัวซ้ำในขั้นที่หกด้วยคู่ author + text

ขั้นที่ 5: ตั้งค่าการหมุน IP และป้องกันการแบน

เป้าหมาย: สอน parser ทำตัวเป็นผู้ใช้ระมัดระวัง เปลี่ยน IP ตามเวลา แนะนำแคปช่า และลดความเร็วอัตโนมัติแทนที่จะชนการแบน

ทำไมเกิดแบน

Yandex ไม่ห้ามดูการ์ด แต่เฝ้าดูความผิดปกติ: หลายร้อยหน้าในหนึ่งนาทีจากที่อยู่เดียว ช่วงเวลาคงที่ระหว่างคำขอ ไม่มีการเคลื่อนไหวเมาส์ ไม่มีคุกกี้ เมื่อสงสัยสะสม จะมีหน้า SmartCaptcha และถ้าดื้อจะจำกัด IP ชั่วคราว กลยุทธ์ของเราไม่ใช่ "เจาะ" การป้องกัน แต่ไม่ให้เหตุผลเปิดใช้

สามกฎ parser Yandex Maps ที่ใจเย็น

  1. จังหวะคน ไม่เกิน 8-12 การ์ดต่อนาทีต่อ IP หยุดแบบสุ่ม ไม่ใช่คงที่
  2. เปลี่ยน IP เป็นระยะ ทุก 25-40 การ์ด หรือทุก 10-15 นาที เรียกเปลี่ยนที่อยู่ มือถือ โปรกซี่ได้ IP ผู้ให้บริการใหม่ในไม่กี่วินาที และประวัติคำขอ "รีเซ็ต" ในมุมมองเว็บ
  3. ถอยทันทีเมื่อแคปช่า เจอการตรวจ ไม่พยายามผ่าน แต่หยุด 2-3 นาที เปลี่ยน IP และทำต่อจากการ์ดเดิมใน context เบราว์เซอร์ใหม่

เพิ่มการหมุนในโค้ด

วางใน parse_cards.py และ parse_reviews.py ฟังก์ชันต่อไปนี้และเรียกในลูปหลัก:

import requests

def change_ip():
    try:
        r = requests.get(CHANGE_IP_URL, timeout=30)
        print('Смена IP:', r.status_code)
    except Exception as e:
        print('Не удалось сменить IP:', e)
    time.sleep(IP_CHANGE_WAIT)

def is_captcha(page):
    if 'showcaptcha' in page.url or 'checkcaptcha' in page.url:
        return True
    return page.query_selector('.CheckboxCaptcha') is not None

def new_page(browser):
    ctx = browser.new_context(locale='ru-RU', viewport={'width': random.choice([1366, 1440, 1536]), 'height': 900})
    return ctx.new_page()

และลูปหลักกลายเป็น:

page = new_page(browser)
since_change = 0
for n, url in enumerate(todo, 1):
    if since_change >= random.randint(25, 40):
        page.context.close()
        change_ip()
        page = new_page(browser)
        since_change = 0
    row = parse_card(page, url)
    if is_captcha(page):
        print('Капча! Пауза и смена IP')
        page.context.close()
        time.sleep(random.uniform(120, 180))
        change_ip()
        page = new_page(browser)
        row = parse_card(page, url)
    writer.writerow(row)
    f.flush()
    since_change += 1
    time.sleep(random.uniform(4, 9))

สิ่งที่ต้องเข้าใจ

  • context ใหม่พร้อม IP ใหม่ ปิด context รีเซ็ตคุกกี้และ local storage เปลี่ยนที่อยู่โดยไม่รีเซ็ตคุกกี้ไม่มีความหมาย เว็บยังจำคุณจาก session
  • ตัวแปร IP_CHANGE_WAIT ใน config.py คือเวลาที่วัดในขั้นแรก ปกติ 15-30 วินาที ตั้งน้อยกว่าไม่ได้ เบราว์เซอร์จะเปิดหน้าผ่านที่อยู่เก่า
  • ขนาดหน้าต่างสุ่ม เพิ่มความหลากหลายใน fingerprint เบราว์เซอร์ เป็นมาตรการอ่อน ๆ แต่ฟรี
  • มือถือ โปรกซี่หมุนตามลิงก์ สะดวกกว่าอื่น คุณไม่ต้องสลับที่อยู่หลายสิบเอง แค่กระตุก URL เดียว

คำแนะนำ: ทำ log ง่าย ๆ: เขียนเวลา หมายเลขการ์ด และเหตุการณ์ (สำเร็จ แคปช่า เปลี่ยน IP) ลงไฟล์ ผ่านสัปดาห์ดู log จะเห็นชัดว่าจังหวะไหนไม่เจอแคปช่าจริง และปรับหยุดให้เหมาะกับช่องทางโปรกซี่

ตรวจสอบ: ทำงานต่อเนื่องหนึ่งชั่วโมง เก็บได้ 400-600 การ์ด ใน terminal มีข้อความแคปช่าไม่เกินหนึ่งสองครั้ง หลังแต่ละครั้งเก็บต่อไปได้อัตโนมัติ IP ผ่านโปรกซี่เปลี่ยนอย่างน้อยสิบครั้ง (เห็นจากบรรทัด "Смена IP: 200")

ปัญหาที่อาจเจอ

  • แคปช่าทุกสิบคาร์ด จังหวะสูงเกินสำหรับช่องทางคุณ เพิ่มหยุดเป็น 8-15 วินาที และเปลี่ยน IP ทุก 15 การ์ด
  • หลังเปลี่ยน IP หน้าไม่โหลด เพิ่ม IP_CHANGE_WAIT: ผู้ให้บริการยังไม่ออกที่อยู่ใหม่
  • ลิงก์เปลี่ยน IP คืน error เรื่องเรียกบ่อย แพ็กเกจส่วนใหญ่มีช่วงเวลาต่ำสุดระหว่างการเปลี่ยน ปกติ 1-2 นาที อย่าเปลี่ยน IP บ่อยกว่า

ขั้นที่ 6: ทำความสะอาดข้อมูลและบันทึกใน Excel

เป้าหมาย: เปลี่ยน CSV ดิบเป็นตาราง Excel เรียบร้อย ไม่ซ้ำ เบอร์โทรปกติ และคะแนนเป็นตัวเลข

เขียนสคริปต์ทำความสะอาด

สร้างไฟล์ clean_export.py:

import pandas as pd

def norm_phone(value):
    out = []
    for raw in str(value).split(';'):
        digits = ''.join(ch for ch in raw if ch.isdigit())
        if len(digits) == 11 and digits[0] in '78':
            out.append('+7' + digits[1:])
        elif len(digits) == 10:
            out.append('+7' + digits)
    return '; '.join(dict.fromkeys(out))

orgs = pd.read_csv('orgs.csv', encoding='utf-8')
orgs = orgs.drop_duplicates(subset='url')
orgs['phones'] = orgs['phones'].fillna('').apply(norm_phone)
orgs['rating'] = pd.to_numeric(orgs['rating'].astype(str).str.replace(',', '.'), errors='coerce')
orgs['reviews_count'] = pd.to_numeric(orgs['reviews_count'].astype(str).str.extract('(\d+)')[0], errors='coerce')
orgs = orgs.sort_values('rating', ascending=False)

reviews = pd.read_csv('reviews.csv', encoding='utf-8')
reviews = reviews.drop_duplicates(subset=['org_url', 'author', 'text'])

with pd.ExcelWriter('yandex_maps_result.xlsx', engine='openpyxl') as w:
    orgs.to_excel(w, sheet_name='Организации', index=False)
    reviews.to_excel(w, sheet_name='Отзывы', index=False)
print('Организаций:', len(orgs), 'Отзывов:', len(reviews))

ในบรรทัดที่ดึงจำนวนรีวิวใช้ regex จาก backslash เดียวและตัวอักษร d ในวงเล็บ: ดึงตัวเลขแรกจากข้อความแบบ "312 отзывов" คัดลอกอย่างระวัง

สคริปต์ทำอะไร

  1. ลบองค์กรซ้ำตาม URL
  2. ทำให้เบอร์ทั้งหมดเป็นรูปแบบ +7XXXXXXXXXX ตัดวงเล็บ ช่องว่าง และขีด รูปแบบนี้สะดวกสำหรับ CRM และเทียบฐาน
  3. เปลี่ยนจุลภาคเป็นจุดในคะแนน เพื่อให้ Excel มองเป็นตัวเลขและเรียงได้
  4. ดึงจำนวนรีวิวจากข้อความ
  5. ลบรีวิวซ้ำและเขียนสองชีตในไฟล์ xlsx เดียว

รัน python clean_export.py แล้วเปิด yandex_maps_result.xlsx ชีตแรกองค์กรเรียงตามคะแนน เบอร์โทรสม่ำเสมอ ชีตที่สองรีวิวผูกกับองค์กรด้วยคอลัมน์ org_url

คำแนะนำ: เพิ่มคอลัมน์ "วันที่เก็บ" ด้วยวันที่ปัจจุบัน อีกเดือนเก็บซ้ำและเทียบตารางด้วย merge ใน pandas: จะเห็นองค์กรใหม่ ร้านที่ปิด และการเปลี่ยนคะแนนคู่แข่ง นี่คือการมอนิเตอร์ตลาดเต็มรูปแบบ

ตรวจสอบ: ไฟล์ xlsx เปิดไม่มีคำเตือน ภาษารัสเซียอ่านได้ คอลัมน์ rating เรียงเป็นตัวเลข คอลัมน์ phones ไม่มีวงเล็บและช่องว่าง จำนวนแถวในชีต "Организации" ตรงกับจำนวนลิงก์ไม่ซ้ำใน links.json ลบ error

ตรวจสอบผลลัพธ์: เช็คลิสต์ parser พร้อมใช้

ไล่ดูรายการ ถ้าตอบ "ใช่" ทุกข้อ Parser Yandex Maps พร้อมทำงานประจำ

เช็คลิสต์

  • check_proxy.py แสดง IP ผู้ให้บริการมือถือ ต่างจากบ้าน
  • collect_links.py เก็บได้มากกว่า 50 ลิงก์ต่อคำค้นกลาง ๆ และหยุดเอง
  • parse_cards.py กรอกชื่อ ที่อยู่ และเบอร์โทรไม่น้อยกว่า 90 เปอร์เซ็นต์ของการ์ด
  • ปุ่ม "แสดงเบอร์โทร" เปิดอัตโนมัติ
  • parse_reviews.py คืนรีวิวพร้อมคะแนน 1-5
  • เมื่อแคปช่า สคริปต์หยุด เปลี่ยน IP และทำต่อโดยไม่ต้องให้คุณทำอะไร
  • หลังหยุดฉุกเฉิน การรันซ้ำทำต่อจากที่ขาด
  • clean_export.py สร้าง xlsx ที่มีสองชีตและเบอร์โทรปกติ

วิธีทดสอบทั้งหมด

  1. ใช้คำค้นเล็ก ๆ ที่ในเมืองมี 30-60 องค์กร เช่น "шиномонтаж" ในเมืองเล็ก
  2. รันสคริปต์ทั้งหมดตามลำดับและจับเวลา 50 การ์ดพร้อมรีวิวควรใช้ 15-25 นาที
  3. เลือกห้าองค์กรสุ่มจากตารางและเทียบเบอร์โทรและที่อยู่กับการด์ด้วยมือ
  4. หยุด parse_cards.py กลางทางด้วย Ctrl+C แล้วรันใหม่ ตรวจว่าตัวนับ "เหลือ" ลดลง ไม่รีเซ็ต

ตัวชี้วัดความสำเร็จ

  • ความแม่นยำเมื่อเทียบด้วยมือ: ตรง 100 เปอร์เซ็นต์ด้านเบอร์โทรและที่อยู่
  • สัดส่วนชื่อว่าง: น้อยกว่า 3 เปอร์เซ็นต์
  • ความถี่แคปช่า: ไม่เกินหนึ่งต่อ 200-300 การ์ด
  • ความเร็ว: 400-600 การ์ดต่อชั่วโมงต่อช่องทางโปรกซี่ที่จังหวะปลอดภัย

ข้อผิดพลาดทั่วไปและวิธีแก้

รวบรวมปัญหาที่เกือบทุกคนเจอเมื่อเขียน Parser Yandex Maps ครั้งแรก และวิธีแก้

ฟิลด์ว่างส่วนใหญ่ของการ์ด

สาเหตุ: Yandex อัปเดตโค้ด class เปลี่ยน วิธีแก้: เปิดการ์ดใน Chrome กด F12 หา class ใหม่และแทนในฟังก์ชัน parse_card เก็บ selector ใน dictionary เดียวตอนต้นไฟล์ เพื่อแก้ที่เดียว

แคปช่ามาตั้งแต่หน้าแรก

สาเหตุ: IP โปรกซี่ "เหนื่อย" จากกิจกรรมก่อน หรือเบราว์เซอร์เปิดด้วยพารามิเตอร์น่าสงสัย วิธีแก้: เปลี่ยน IP ก่อนเริ่ม ตรวจว่า locale='ru-RU' ตั้งแล้ว และอย่าใช้โหมด headless ในการรันแรก เพราะให้สัญญาณ automation มากกว่า

การเลื่อนรายการเลื่อนแผนที่ ไม่ใช่แผง

สาเหตุ: เคอร์เซอร์เมาส์อยู่นอกแผงซ้าย วิธีแก้: ปรับพิกัด page.mouse.move ตามขนาดหน้าต่าง ที่ความกว้าง 1400 พิกเซลแผงใช้ประมาณ 450 พิกเซลแรกแนวนอน

เก็บได้ 20 องค์กรเดิม

สาเหตุ: แผงไม่เลื่อนสุด ตัวนับ stale ทำงานเร็วเกิน วิธีแก้: เพิ่มหยุดหลังเลื่อนเป็น 3-4 วินาที และเพิ่ม stale เป็น 6 เพราะ Maps บางครั้งโหลดชุดถัดไปช้า

เบอร์เห็นในเบราว์เซอร์ แต่ในตารางว่าง

สาเหตุ: หมายเลขปรากฏหลังคลิก แต่สคริปต์เก็บข้อมูลก่อนเสร็จ หรือปุ่มมี class อื่น วิธีแก้: เพิ่มหยุดหลังคลิกเป็น 2-3 วินาทีและตรวจ class ปุ่ม

Error Target closed หรือ Browser has been closed

สาเหตุ: ปิด context ตอนเปลี่ยน IP แต่ยังใช้ page เก่า วิธีแก้: ตรวจว่าหลัง page.context.close() ตัวแปร page ถูกกำหนดใหม่ผ่าน new_page(browser) เหมือนตัวอย่างขั้นที่ 5

หลังเปลี่ยน IP หน้าโหลดไม่จบ

สาเหตุ: ผู้ให้บริการยังไม่ออกที่อยู่ใหม่ โปรกซี่อยู่ระหว่างเปลี่ยน วิธีแก้: เพิ่ม IP_CHANGE_WAIT เป็น 30-40 วินาทีและเพิ่ม timeout ใน page.goto เพื่อไม่ให้ค้างบล็อกลูป

Excel เปิด CSV เป็นตัวอักษรประหลาด

สาเหตุ: Excel ไม่รู้จัก UTF-8 โดยไม่มี BOM วิธีแก้: ใช้ clean_export.py และทำงานกับ xlsx หรือตอนเขียน CSV ระบุ encoding='utf-8-sig'

ความสามารถเพิ่มเติมสำหรับขั้นสูง

Parser พื้นฐานแก้ปัญหา 90 เปอร์เซ็นต์แล้ว ถ้าต้องการเร็วและข้อมูลมากขึ้น นี่คือทางต่อ

จับ network response แทนแยกโค้ด

Maps โหลดข้อมูลการ์ดเป็น JSON ในคำขอแยก Playwright รับฟังได้ผ่าน page.on('response', handler) ใน handler ตรวจว่า response.url มี /maps/api/ ไหม และบันทึก response.json() ข้อดี: ข้อมูลมีโครงสร้างและไม่ขึ้นกับ class โค้ด ข้อเสีย: ที่อยู่ภายในเปลี่ยนโดยไม่เตือน และแยก JSON ซ้อนยากกว่าอ่านข้อความจากหน้า วิธีนี้ดีเมื่อใช้ร่วมกับหลัก: ถ้ามี JSON response ใช้เลย ไม่งั้นแยก HTML

ทำงาน параллельноหลายช่องทางโปรกซี่

มือถือ โปรกซี่เดียวที่จังหวะปลอดภัยให้ 400-600 การ์ดต่อชั่วโมง ถ้าต้องการเร็วขึ้น ซื้อสองสามช่องทางและรันโปรเซสแยกต่อช่องทาง แบ่ง links.json เป็นส่วนเท่า ๆ อย่ารันหลายเบราว์เซอร์ผ่านช่องทางเดียว จังหวะรวมต่อ IP จะเพิ่มและแคปช่ากลับมา สำหรับ orchestration ใช้สคริปต์เปิดง่าย ๆ ด้วย subprocess หรือไลบรารี asyncio กับ async_playwright ที่แต่ละ worker ได้ context และโปรกซี่ของตัวเองในพารามิเตอร์ proxy ของ new_context

มอนิเตอร์การเปลี่ยนแปลง

บันทึกผลแต่ละครั้งในไฟล์แยกตามวันที่และเทียบด้วย org_id: รหัสที่ใหม่คือผู้เล่นใหม่ รหัสที่หายคือร้านปิด การเปลี่ยน rating และ reviews_count คือพลวัตชื่อเสียง ตั้งรันทุกสองสัปดาห์ผ่าน Task Scheduler ของ Windows หรือ cron แล้วคุณจะมีแผนที่ niche แบบสด

ขยายฟิลด์

ในการ์ดมีบล็อกมีประโยชน์อื่น: รายการบริการพร้อมราคา ลิงก์โซเชียล เครื่องหมาย "องค์กรยืนยัน" จำนวนภาพ ถนนที่ใกล้สุด แต่ละฟิลด์เพิ่มด้วยวิธีเดียวกัน หา class ผ่าน F12 เพิ่ม grab ใน dictionary บล็อกราคามีค่ามากสำหรับนัก Arbitrage ที่ประเมินค่าเฉลี่ยต่อบิลใน niche

วิเคราะห์รีวิว

ข้อความที่เก็บเหมาะกับการวิเคราะห์ง่าย ๆ: นับความถี่คำในรีวิวดาวหนึ่งสองแล้วได้รายการข้อร้องเรียนหลักของลูกค้าต่อคู่แข่ง ใช้ pandas และ Counter จาก stdlib ก็พอ ฉบับสูงใช้โมเดลภาษาแยกตามหัวข้อ: ราคา คุณภาพ บริการ รอ

API ทางการเป็นทางเลือก

สำหรับโปรเจกต์เชิงพาณิชย์ขนาดใหญ่ พิจารณา API Yandex สำหรับค้นหาองค์กร ให้ชื่อ ที่อยู่ เบอร์โทร และหมวดหมู่ในรูปแบบมีโครงสร้าง และไม่มีความเสี่ยงแบนเลย ไม่มีรีวิว จำกัดการชำระ แต่สำหรับเก็บฐานติดต่อนี่คือทางที่สะอาดที่สุด Parser การ์ดยังเป็นเครื่องมือสำหรับรีวิวและฟิลด์ที่ไม่มีใน API

FAQ: คำถามพบบ่อยเรื่องการแยกข้อมูล Yandex Maps

เก็บเบอร์โทรองค์กรจาก Yandex Maps ถูกกฎหมายไหม

ข้อมูลติดต่อบริษัทเผยแพร่สาธารณะโดยองค์กรเอง ไม่ใช่ข้อมูลส่วนบุคคล การเก็บเพื่อวิเคราะห์เองยอมรับได้ แต่การใช้เบอร์เหล่านี้โทรและส่งข้อความจำนวนมากโดยไม่ได้รับความยินยอมผิดกฎหมายโฆษณา จำข้อจำกัดข้อตกลงผู้ใช้ Yandex เรื่องการเก็บอัตโนมัติและให้โหลดน้อย

ทำไมใช้ requests ธรรมดาไม่มีเบราว์เซอร์ไม่ได้

Maps วาดด้วยโค้ด JavaScript ทั้งหมด เซิร์ฟเวอร์ส่ง HTML เกือบว่าง ข้อมูลโหลดทีหลัง requests จะได้แค่โครงไม่มีเบอร์และที่อยู่ จึงต้อง Playwright กับ Chromium จริง

ต้องใช้มือถือ โปรกซี่ไหม แยกจาก IP บ้านได้ไหม

ทางเทคนิค 50-100 การ์ดแรกเก็บได้ แต่หลังจากนั้นแคปช่ามา และ IP บ้านจะถูกจำกัดหลายชั่วโมง ใช้ Yandex ปกติไม่ได้ มือถือ โปรกซี่แก้ปัญหาสองทาง: ที่อยู่ผู้ให้บริการมือถือไว้วางใจกว่าแต่แรก และหมุนตามลิงก์กระจายโหลดระหว่างที่อยู่โดยไม่หยุดเก็บ

เก็บได้กี่การ์ดต่อวันต่อโปรกซี่

ที่จังหวะปลอดภัย 8-12 การ์ดต่อนาทีพร้อมหยุดและเปลี่ยน IP ทุก 30 การ์ด ประมาณ 5-8 พันต่อวันทำงานต่อเนื่อง พร้อมรีวิวปริมาณลดลงสองสามเท่า เพราะหน้ารีวิวต้องเลื่อน

ถ้า Yandex เปลี่ยนโค้ดและ parser พังทำอย่างไร

เป็นสถานการณ์ปกติ เกิดไม่กี่ครั้งต่อปี เปิดการ์ด กด F12 หา class ใหม่ของ element และแทนในโค้ด ใช้เวลา 10-15 นาที เพื่อให้ง่าย เก็บ selector ทั้งหมดใน dictionary เดียวตอนต้นไฟล์

เก็บองค์กรทั้งจังหวัด ไม่ใช่เมืองเดียวได้ไหม

ทำรายการ населенных пунктов และรัน collect_links.py ในลูป ใส่ชื่อใน QUERY รวมลิงก์ใน set เดียว สำหรับเมืองใหญ่แตกตามเขตเพิ่ม เพราะคำค้นเดียวไม่ค่อยให้เกิน 500 ผลลัพธ์

รัน parser เบื้องหลังไม่มีหน้าต่างเบราว์เซอร์ได้ไหม

ได้ ตั้ง headless=True แต่ทำหลังดีบัก selector และยืนยันแล้วว่าแคปช่าไม่มา ในโหมดไม่มีหน้าต่างสังเกตปัญหายาก และสัญญาณ automation บางอย่างชัดกว่า ทางสายกลาง: headless=True บวก screenshot หน้าเมื่อ error ทุกครั้งผ่าน page.screenshot(path='error.png')

รู้ได้ไงว่าสคริปต์เจอแคปช่าถ้าไม่ดูหน้าจอ

ฟังก์ชัน is_captcha จากขั้นที่ 5 ตรวจที่อยู่หน้าและมีบล็อกตรวจไหม เพิ่มการแจ้งเตือน เช่นเขียน log หรือข้อความใน messenger ผ่านบอท แล้วรู้ทันที

รีวิวเก็บไม่หมด แค่ร้อยหลัง จะได้มากกว่านี้ไหม

เพิ่ม max_scrolls ใน parse_reviews เป็น 50-100 จำไว้ว่าแต่ละการเลื่อนคือคำขอเพิ่มไปเซิร์ฟเวอร์ ดังนั้นองค์กรที่มีรีวิวหลายพันจะใช้เวลาหลายนาทีและต้องเปลี่ยน IP บ่อยขึ้น

วิธีนี้ดีกว่าบริการแยกข้อมูลสำเร็จรูปยังไง

คุณควบคุมฟิลด์ จังหวะ และความสดของข้อมูลเต็มที่ ไม่จ่ายต่อบรรทัด และไม่ขึ้นกับตารางอัปเดตของคนอื่น บริการสำเร็จรูปสะดวกสำหรับงานครั้งเดียวสองสามร้อยการ์ด แต่ Parser Yandex Maps ของตัวเองคืนทุนแค่การเก็บครั้งที่สอง

บทสรุป

มาสรุปกัน คุณติดตั้ง Python และ Playwright เชื่อมต่อมือถือ โปรกซี่ และตรวจการเปลี่ยน IP เก็บลิงก์การ์ดองค์กรตามคำค้นและเมือง เขียนฟังก์ชันเปิดทุกการ์ด เปิดเบอร์ที่ซ่อน และดึงชื่อ ที่อยู่ เว็บไซต์ คะแนน และเวลาทำการ เพิ่มเก็บรีวิวพร้อมคะแนน สอน parser เปลี่ยน IP ตามเวลาและตอบสนองแคปช่าอย่างถูกต้อง สุดท้ายทำความสะอาดข้อมูลและได้ Excel เรียบร้อยสองชีต

สิ่งสำคัญที่ควรจำ: ความเสถียรของ Parser Yandex Maps ขึ้นกับสามอย่าง ไม่ใช่ลูกเล่น — จังหวะคน หมุนที่อยู่เป็นระยะผ่านมือถือ โปรกซี่ และพร้อมหยุดเมื่อสัญญาณแรก สคริปต์ที่เคารพทรัพยากรทำงานได้เป็นเดือนโดยไม่ต้องยุ่ง

ทำอะไรต่อ

  • รันเก็บเต็มรูปแบบครั้งแรกใน niche ของคุณและตรวจห้าถึงสิบการ์ดด้วยมือ
  • ตั้งเก็บทุกสองสัปดาห์และเริ่มสะสมประวัติการเปลี่ยนแปลง
  • ลองจับ JSON response จากส่วนขั้นสูง เพื่อพึ่งโค้ดน้อยลง
  • ถ้าปริมาณเพิ่ม เพิ่มช่องทางโปรกซี่ที่สองและรันขนาน

ไปทางไหนต่อ

ขั้นถัดไปที่สมเหตุสมผลคือวิเคราะห์รีวิวที่เก็บอัตโนมัติและเชื่อมตารางกับ CRM หรือโฆษณา ถ้าคุณทำงานหลายแพลตฟอร์ม วิธีเดียวกันกับ Playwright และมือถือ โปรกซี่ใช้ได้กับเว็บที่โหลดแบบไดนามิกใดก็ได้ หลักการเหมือนกัน แค่ selector ต่าง เก็บข้อมูลสนุกและข้อมูลสะอาดนะครับ!