Xây dựng trình thu thập dữ liệu web bằng Python

Thu thập dữ liệu web là một kỹ thuật mạnh mẽ để thu thập dữ liệu từ web bằng cách tìm tất cả các URL cho một hoặc nhiều miền. Python có một số thư viện và khung thu thập dữ liệu web phổ biến

Trong bài viết này, trước tiên chúng tôi sẽ giới thiệu các chiến lược thu thập thông tin khác nhau và các trường hợp sử dụng. Sau đó, chúng ta sẽ xây dựng một trình thu thập dữ liệu web đơn giản từ đầu bằng Python bằng hai thư viện. Yêu cầu và súp đẹp. Tiếp theo, chúng ta sẽ xem tại sao nên sử dụng khung thu thập dữ liệu web như Scrapy. Cuối cùng, chúng ta sẽ xây dựng một ví dụ về trình thu thập dữ liệu với Scrapy để thu thập siêu dữ liệu phim từ IMDb và xem cách Scrapy chia tỷ lệ cho các trang web có vài triệu trang

Trình thu thập dữ liệu web là gì?

Thu thập dữ liệu web và quét web là hai khái niệm khác nhau nhưng có liên quan. Thu thập dữ liệu web là một thành phần của quét web, logic của trình thu thập thông tin tìm thấy các URL sẽ được xử lý bằng mã trình quét

Trình thu thập dữ liệu web bắt đầu với một danh sách các URL để truy cập, được gọi là hạt giống. Đối với mỗi URL, trình thu thập thông tin sẽ tìm các liên kết trong HTML, lọc các liên kết đó dựa trên một số tiêu chí và thêm các liên kết mới vào hàng đợi. Tất cả HTML hoặc một số thông tin cụ thể được trích xuất để được xử lý bởi một đường dẫn khác


Chiến lược thu thập dữ liệu web

Trong thực tế, trình thu thập dữ liệu web chỉ truy cập vào một tập hợp con các trang tùy thuộc vào ngân sách của trình thu thập thông tin, có thể là số trang tối đa trên mỗi miền, độ sâu hoặc thời gian thực hiện

Nhiều trang web cung cấp một robot. txt để cho biết đường dẫn nào của trang web có thể được thu thập thông tin và đường dẫn nào bị cấm. Ngoài ra còn có sơ đồ trang web. xml, rõ ràng hơn một chút so với robot. txt và hướng dẫn cụ thể các bot nên thu thập dữ liệu đường dẫn nào và cung cấp siêu dữ liệu bổ sung cho mỗi URL

Các trường hợp sử dụng trình thu thập dữ liệu web phổ biến bao gồm

  • Công cụ tìm kiếm [e. g. Googlebot, Bingbot, Yandex Bot…] thu thập tất cả HTML cho một phần quan trọng của Web. Dữ liệu này được lập chỉ mục để làm cho nó có thể tìm kiếm được
  • Các công cụ phân tích SEO ngoài việc thu thập HTML còn thu thập siêu dữ liệu như thời gian phản hồi, trạng thái phản hồi để phát hiện các trang bị hỏng và các liên kết giữa các miền khác nhau để thu thập các liên kết ngược
  • Các công cụ giám sát giá thu thập dữ liệu các trang web thương mại điện tử để tìm các trang sản phẩm và trích xuất siêu dữ liệu, đặc biệt là giá. Các trang sản phẩm sau đó được xem lại định kỳ
  • Thu thập thông tin chung duy trì kho lưu trữ dữ liệu thu thập thông tin web mở. Ví dụ: kho lưu trữ từ tháng 5 năm 2022 chứa 3. 45 tỷ trang web

Tiếp theo, chúng ta sẽ so sánh ba chiến lược khác nhau để xây dựng trình thu thập dữ liệu web bằng Python. Đầu tiên, chỉ sử dụng các thư viện tiêu chuẩn, sau đó là các thư viện của bên thứ ba để thực hiện các yêu cầu HTTP và phân tích cú pháp HTML, và cuối cùng là khung thu thập dữ liệu web

Xây dựng trình thu thập dữ liệu web đơn giản bằng Python từ đầu

Để xây dựng trình thu thập dữ liệu web đơn giản bằng Python, chúng tôi cần ít nhất một thư viện để tải xuống HTML từ một URL và một thư viện khác để trích xuất các liên kết. Python cung cấp các thư viện chuẩn urllib để thực hiện các yêu cầu HTTP và html. trình phân tích cú pháp để phân tích cú pháp HTML. Có thể tìm thấy một ví dụ về trình thu thập thông tin Python chỉ được xây dựng với các thư viện tiêu chuẩn trên Github

Ngoài ra còn có các thư viện phổ biến khác, chẳng hạn như Yêu cầu và Súp đẹp, có thể cung cấp trải nghiệm nhà phát triển được cải thiện khi soạn yêu cầu HTTP và xử lý tài liệu HTML. Nếu bạn muốn tìm hiểu thêm, bạn có thể xem hướng dẫn này về ứng dụng khách Python HTTP tốt nhất

Bạn có thể cài đặt hai thư viện cục bộ

pip install requests bs4

Trình thu thập thông tin cơ bản có thể được xây dựng theo sơ đồ kiến ​​trúc trước đó

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]

Bot của chúng tôi ở đây định nghĩa một lớp

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
6 với một vài phương thức trợ giúp [
import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
7 sử dụng thư viện Yêu cầu,
import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
8 sử dụng thư viện Beautiful Soup và
import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
9 để lọc URL] và sau đó tiếp tục bằng cách khởi tạo lớp với URL bắt đầu IMDb của chúng tôi và gọi phương thức
python crawler.py
0 của nó

python crawler.py
1 sẽ chạy miễn là có các URL đang chờ xử lý trong
python crawler.py
2, sẽ chuyển từng URL tới
python crawler.py
3, trích xuất bất kỳ liên kết nào và thêm chúng vào
python crawler.py
2 - rửa sạch và lặp lại

Để chạy trình thu thập thông tin của chúng tôi, chỉ cần nhập lệnh này trên dòng lệnh của bạn

python crawler.py

Trình thu thập thông tin ghi lại một dòng cho mỗi URL đã truy cập

INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr

Mã này rất đơn giản nhưng có nhiều vấn đề về hiệu suất và khả năng sử dụng cần giải quyết trước khi thu thập dữ liệu thành công một trang web hoàn chỉnh

  • Trình thu thập thông tin chậm và không hỗ trợ xử lý song song. Như có thể thấy từ dấu thời gian, mất khoảng một giây để thu thập dữ liệu từng URL. Mỗi khi trình thu thập thông tin đưa ra yêu cầu, nó sẽ đợi phản hồi và không làm gì khác
  • Logic URL tải xuống không có cơ chế thử lại, hàng đợi URL không phải là hàng đợi thực và không hiệu quả với số lượng lớn URL
  • Logic trích xuất liên kết không hỗ trợ chuẩn hóa URL bằng cách xóa các tham số chuỗi truy vấn URL, không xử lý các URL neo/đoạn tương đối [i. e.
    python crawler.py
    
    5] và không hỗ trợ lọc URL theo tên miền hoặc lọc các yêu cầu đối với tệp tĩnh
  • Trình thu thập thông tin không tự nhận dạng và bỏ qua rô-bốt. tập tin txt

Tiếp theo, chúng ta sẽ xem cách Scrapy cung cấp tất cả các chức năng này và giúp dễ dàng mở rộng cho các lần thu thập thông tin tùy chỉnh của bạn

Thu thập dữ liệu web với Scrapy

Scrapy là khung Python thu thập và quét web phổ biến nhất với gần 50 nghìn sao trên Github. Một trong những ưu điểm của Scrapy là các yêu cầu được lên lịch và xử lý không đồng bộ. Điều này có nghĩa là Scrapy có thể gửi một yêu cầu khác trước khi yêu cầu trước đó hoàn thành hoặc thực hiện một số công việc khác ở giữa. Scrapy có thể xử lý nhiều yêu cầu đồng thời nhưng cũng có thể được định cấu hình để tôn trọng các trang web bằng cài đặt tùy chỉnh, như chúng ta sẽ thấy sau

Scrapy có kiến ​​trúc đa thành phần. Thông thường, bạn sẽ triển khai ít nhất hai lớp khác nhau. Nhện và đường ống. Quét web có thể được coi là một ETL nơi bạn trích xuất dữ liệu từ web và tải nó vào bộ lưu trữ của riêng bạn. Nhện trích xuất dữ liệu và đường ống tải dữ liệu vào bộ lưu trữ. Quá trình chuyển đổi có thể xảy ra cả trong trình thu thập thông tin và quy trình, nhưng tôi khuyên bạn nên đặt một quy trình Scrapy tùy chỉnh để chuyển đổi từng mục một cách độc lập với nhau. Bằng cách này, việc không xử lý một mục sẽ không ảnh hưởng đến các mục khác

Trên hết, bạn có thể thêm phần mềm trung gian trình tải xuống và trình tải xuống ở giữa các thành phần như có thể thấy trong sơ đồ bên dưới


Tổng quan về Kiến trúc Scrapy []

Nếu bạn đã sử dụng Scrapy trước đây, bạn sẽ biết rằng trình quét web được định nghĩa là một lớp kế thừa từ lớp Spider cơ sở và triển khai một phương thức phân tích cú pháp để xử lý từng phản hồi. Nếu bạn chưa quen với Scrapy, bạn có thể đọc bài viết này để cạo dễ dàng với Scrapy

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass

Scrapy cũng cung cấp một số. CrawlSpider, XMLFeedSpider, CSVFeedSpider và Sơ đồ trang web. Lớp kế thừa từ lớp Spider cơ sở và cung cấp một thuộc tính quy tắc bổ sung để xác định cách thu thập dữ liệu trang web. Mỗi quy tắc sử dụng a để chỉ định liên kết nào được trích xuất từ ​​​​mỗi trang. Tiếp theo, chúng ta sẽ xem cách sử dụng từng cái trong số chúng bằng cách xây dựng trình thu thập thông tin cho IMDb, Cơ sở dữ liệu phim trên Internet

Xây dựng một ví dụ Scrapy crawler cho IMDb

Trước khi thử thu thập dữ liệu IMDb, tôi đã kiểm tra robot IMDb. txt để xem đường dẫn URL nào được phép. Tệp rô bốt chỉ không cho phép 26 đường dẫn cho tất cả tác nhân người dùng. Scrapy đọc các robot. txt trước và tôn trọng nó khi cài đặt được đặt thành

python crawler.py
6. Đây là trường hợp của tất cả các dự án được tạo bằng lệnh Scrapy
python crawler.py
7

scrapy startproject scrapy_crawler

Lệnh này tạo một dự án mới với cấu trúc thư mục dự án Scrapy mặc định

scrapy_crawler/

├── scrapy.cfg
└── scrapy_crawler
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py

Sau đó, bạn có thể tạo một con nhện trong

python crawler.py
8 với quy tắc trích xuất tất cả các liên kết

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class ImdbCrawler[CrawlSpider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']
    rules = [Rule[LinkExtractor[]],]

Bây giờ, chỉ cần khởi chạy trình thu thập thông tin bằng lệnh

python crawler.py
9

scrapy crawl imdb --logfile imdb.log

Bạn sẽ nhận được rất nhiều nhật ký, bao gồm một nhật ký cho mỗi yêu cầu. Khám phá nhật ký, tôi nhận thấy rằng ngay cả khi chúng tôi đặt

INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
0 để chỉ thu thập dữ liệu các trang web dưới https. //www. imdb. com, đã có yêu cầu đối với các miền bên ngoài, chẳng hạn như amazon. com


[scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting [302] to <GET //www.amazon.com/b/?&node=5160028011&ref_=ft_iba> from <GET [//www.imdb.com/whitelist-offsite?url=https%3A%2F%2Fwww.amazon.com%2Fb%2F%3F%26node%3D5160028011%26ref_%3Dft_iba&page-action=ft-iba&ref=ft_iba][//www.imdb.com/whitelist-offsite?url=https%3A%2F%2Fwww.amazon.com%2Fb%2F%3F%26node%3D5160028011%26ref_%3Dft_iba&page-action=ft-iba&ref=ft_iba]>

IMDb chuyển hướng các đường dẫn bên dưới

INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
1 và
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
2 sang các miền bên ngoài. Có một vấn đề mở về Scrapy Github cho thấy rằng các URL bên ngoài không được lọc ra khi
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
3 được áp dụng trước
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
4. Để khắc phục sự cố này, chúng tôi có thể định cấu hình trình trích xuất liên kết để bỏ qua các URL bắt đầu bằng hai biểu thức chính quy

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
0

Các lớp

INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
5 và
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
6 hỗ trợ một số đối số để lọc URL. Ví dụ: bạn có thể bỏ qua các phần mở rộng tệp cụ thể và giảm số lượng URL trùng lặp bằng cách sắp xếp hoặc thu gọn các chuỗi truy vấn

Nếu bạn không tìm thấy đối số cụ thể cho trường hợp sử dụng của mình, bạn có thể sử dụng tham số

INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
7 của hoặc
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
9 của. Ví dụ: chúng tôi đã nhận được cùng một trang hai lần, một lần dưới dạng URL đơn giản, lần khác với các tham số chuỗi truy vấn bổ sung

  • https. //www. imdb. com/tên/nm1156914/
  • https. //www. imdb. com/name/nm1156914/?mode=desktop&ref_=m_ft_dsk

Để giới hạn số lượng URL được thu thập thông tin, chúng tôi có thể xóa tất cả các chuỗi truy vấn khỏi URL bằng hàm từ thư viện

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
2 và sử dụng nó trong
INFO:Crawling: //www.imdb.com/
INFO:Crawling: //www.imdb.com/?ref_=nv_home
INFO:Crawling: //www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: //www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: //www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: //www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: //www.imdb.com/feature/genre/?ref_=nv_ch_gr
9

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
1

Bây giờ chúng tôi đã giới hạn số lượng yêu cầu để xử lý, chúng tôi có thể thêm một phương pháp

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
4 để trích xuất dữ liệu từ mỗi trang và chuyển nó đến một đường dẫn để lưu trữ nó. Ví dụ: chúng tôi có thể xử lý
from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
5 trong một quy trình khác hoặc chọn siêu dữ liệu HTML

Để chọn siêu dữ liệu HTML trong thẻ tiêu đề, chúng tôi có thể chỉ định các biểu thức XPath của riêng mình nhưng tôi thấy tốt hơn là sử dụng thư viện, trích xuất, trích xuất tất cả siêu dữ liệu từ trang HTML. Bạn có thể cài đặt nó với

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
6

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
2

Tôi đặt thuộc tính

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
7 thành
from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
8 để Scrapy vẫn theo dõi tất cả các liên kết từ mỗi phản hồi, ngay cả khi chúng tôi đã cung cấp phương pháp phân tích cú pháp tùy chỉnh. Tôi cũng đã định cấu hình giải nén để chỉ trích xuất siêu dữ liệu Open Graph và JSON-LD, một phương pháp phổ biến để mã hóa dữ liệu được liên kết bằng JSON trong Web, được IMDb sử dụng. Bạn có thể chạy trình thu thập thông tin và lưu trữ các mục ở định dạng dòng JSON vào một tệp

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
3

Tệp đầu ra

from scrapy.spiders import Spider

class ImdbSpider[Spider]:
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['//www.imdb.com/']

    def parse[self, response]:
        pass
9 chứa một dòng cho mỗi mục được tìm kéo. Ví dụ: siêu dữ liệu Open Graph được trích xuất, dành cho một bộ phim được lấy từ các thẻ
scrapy startproject scrapy_crawler
0 trong HTML, trông như thế này

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
4

JSON-LD cho một mục quá dài để đưa vào bài viết, đây là một ví dụ về những gì Scrapy trích xuất từ ​​thẻ

scrapy startproject scrapy_crawler
1

import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig[
    format='%[asctime]s %[levelname]s:%[message]s',
    level=logging.INFO]

class Crawler:

    def __init__[self, urls=[]]:
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url[self, url]:
        return requests.get[url].text

    def get_linked_urls[self, url, html]:
        soup = BeautifulSoup[html, 'html.parser']
        for link in soup.find_all['a']:
            path = link.get['href']
            if path and path.startswith['/']:
                path = urljoin[url, path]
            yield path

    def add_url_to_visit[self, url]:
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append[url]

    def crawl[self, url]:
        html = self.download_url[url]
        for url in self.get_linked_urls[url, html]:
            self.add_url_to_visit[url]

    def run[self]:
        while self.urls_to_visit:
            url = self.urls_to_visit.pop[0]
            logging.info[f'Crawling: {url}']
            try:
                self.crawl[url]
            except Exception:
                logging.exception[f'Failed to crawl: {url}']
            finally:
                self.visited_urls.append[url]

if __name__ == '__main__':
    Crawler[urls=['//www.imdb.com/']].run[]
5

Khi khám phá nhật ký, tôi nhận thấy một vấn đề phổ biến khác với trình thu thập dữ liệu. Bằng cách nhấp liên tục vào các bộ lọc, trình thu thập thông tin sẽ tạo các URL có cùng nội dung, chỉ có điều các bộ lọc được áp dụng theo một thứ tự khác

  • https. //www. imdb. com/name/nm2900465/videogallery/content_type-trailer/relative_titles-tt0479468
  • https. //www. imdb. com/name/nm2900465/videogallery/ related_titles-tt0479468/content_type-trailer

URL tìm kiếm và bộ lọc dài là một vấn đề khó có thể được giải quyết một phần bằng cách giới hạn độ dài của URL bằng cài đặt Scrapy,

Tôi đã sử dụng IMDb làm ví dụ để hiển thị kiến ​​thức cơ bản về xây dựng trình thu thập dữ liệu web bằng Python. Tôi đã không để trình thu thập thông tin chạy lâu vì tôi không có trường hợp sử dụng cụ thể cho dữ liệu. Trong trường hợp bạn cần dữ liệu cụ thể từ IMDb, bạn có thể kiểm tra dự án Bộ dữ liệu IMDb cung cấp khả năng xuất dữ liệu IMDb hàng ngày hoặc Cinemagoer, một gói Python dành riêng cho việc tìm nạp và xử lý dữ liệu IMDb

Thu thập dữ liệu web trên quy mô lớn

Nếu bạn cố gắng thu thập dữ liệu một trang web lớn như IMDb, với hơn 130 triệu trang [ít nhất là theo Google], điều quan trọng là phải thu thập dữ liệu một cách có trách nhiệm bằng cách điều chỉnh trình thu thập dữ liệu của bạn và điều chỉnh cài đặt của nó cho phù hợp

  1. - Cho phép bạn chỉ định tác nhân người dùng và cung cấp chi tiết liên hệ có thể
  2. - Chỉ định số giây trình thu thập thông tin của bạn sẽ đợi giữa các yêu cầu
  3. - Cho biết số lượng yêu cầu tối đa đồng thời mà trình thu thập thông tin của bạn sẽ gửi đến một trang web
  4. - Cho phép điều chỉnh yêu cầu tự động và động

Lưu ý rằng thu thập dữ liệu Scrapy được tối ưu hóa cho một miền theo mặc định. Nếu bạn đang thu thập dữ liệu trên nhiều miền, hãy kiểm tra các cài đặt này để tối ưu hóa cho thu thập dữ liệu rộng, bao gồm cả việc thay đổi thứ tự thu thập dữ liệu mặc định từ độ sâu trước thành hơi thở trước. Để giới hạn ngân sách thu thập dữ liệu của mình, bạn có thể giới hạn số lượng yêu cầu bằng cài đặt CLOSESPIDER_PAGECOUNT của

Với cài đặt mặc định, Scrapy thu thập dữ liệu khoảng 600 trang mỗi phút cho một trang web như IMDb. Thu thập dữ liệu 130 triệu trang sẽ mất khoảng nửa năm với tốc độ đó chỉ bằng một robot. Nếu bạn cần thu thập dữ liệu nhiều trang web, tốt hơn là khởi chạy các trình thu thập dữ liệu riêng cho từng trang web lớn hoặc nhóm trang web. Nếu quan tâm đến việc thu thập dữ liệu web phân tán, bạn có thể đọc cách một nhà phát triển đã thu thập dữ liệu 250 triệu trang bằng Python trong vòng chưa đầy hai ngày bằng cách sử dụng 20 phiên bản máy Amazon EC2

Trong một số trường hợp, bạn có thể gặp phải các trang web yêu cầu bạn thực thi mã JavaScript để hiển thị tất cả HTML. Không làm như vậy và bạn không thể thu thập tất cả các liên kết trên trang web. Bởi vì ngày nay, việc các trang web hiển thị động nội dung trong trình duyệt là điều rất phổ biến, tôi đã viết một phần mềm trung gian Scrapy để hiển thị các trang JavaScript bằng cách sử dụng API của ScrapingBee

Phần kết luận

Chúng tôi đã so sánh mã của trình thu thập thông tin Python bằng thư viện của bên thứ ba để tải xuống URL và phân tích cú pháp HTML với trình thu thập thông tin được tạo bằng khung thu thập thông tin web phổ biến. Scrapy là một khung thu thập dữ liệu web rất hiệu quả và dễ dàng mở rộng bằng mã tùy chỉnh của bạn. Nhưng bạn cần biết tất cả những nơi bạn có thể móc mã của riêng mình và cài đặt cho từng thành phần

Định cấu hình Scrapy đúng cách càng trở nên quan trọng hơn khi thu thập dữ liệu các trang web có hàng triệu trang. Nếu bạn muốn tìm hiểu thêm về cách thu thập dữ liệu web, tôi khuyên bạn nên chọn một trang web phổ biến và cố gắng thu thập dữ liệu trang web đó. Bạn chắc chắn sẽ gặp những vấn đề mới, điều này làm cho chủ đề trở nên hấp dẫn

💡 Mặc dù bài viết này đề cập đến khá nhiều chủ đề nâng cao hơn [e. g. điều tiết yêu cầu] vẫn còn một vài chi tiết cần tính đến để thu thập dữ liệu trang web thành công

Ví dụ: một số trang web chỉ phân phát nội dung bị giới hạn địa lý, thực hiện lấy dấu vân tay của trình duyệt hoặc yêu cầu khách hàng giải CAPTCHA. Có một bài viết khác đề cập đến những vấn đề này và đi vào chi tiết về cách bạn có thể quét web mà không bị chặn, vui lòng xem thử

Phải thừa nhận rằng việc tìm kiếm bộ chọn CSS phù hợp có thể rất thú vị, nhưng nếu dự án của bạn đang được lên lịch, bạn có thể không muốn phải đối phó với các tác nhân người dùng, địa chỉ IP phân tán, giới hạn tốc độ và phiên bản trình duyệt không đầu. Và đó chính xác là những gì ScrapingBee được tạo ra và nơi nó có thể giúp bạn có trải nghiệm quét web SaaS không rắc rối - vui lòng kiểm tra nền tảng trích xuất dữ liệu không cần mã của chúng tôi

nguồn

  • Định cấu hình Scrapy để thu thập thông tin rộng
  • Cách thu thập dữ liệu một phần tư tỷ trang web trong 40 giờ
  • Làm cách nào để thực thi JavaScript với Scrapy?

Ari Bajo

Ari là một Kỹ sư dữ liệu lão luyện và là một nhà văn kỹ thuật tài năng. Anh ấy đã viết toàn bộ phần tích hợp Scrapy cho ScrapingBee và bài báo tuyệt vời này.

Python có thể được sử dụng cho trình thu thập dữ liệu web không?

Thu thập dữ liệu web là một kỹ thuật hiệu quả để thu thập dữ liệu từ web bằng cách tìm tất cả các URL cho một hoặc nhiều miền. Python có một số thư viện và khung thu thập dữ liệu web phổ biến .

Bạn có thể xây dựng một công cụ quét web bằng Python không?

Xây dựng Trình quét web đầu tiên của bạn . Đặc biệt, urllib. mô-đun yêu cầu chứa một hàm gọi là urlopen[] mà bạn có thể sử dụng để mở một URL trong một chương trình. One useful package for web scraping that you can find in Python's standard library is urllib , which contains tools for working with URLs. In particular, the urllib. request module contains a function called urlopen[] that you can use to open a URL within a program.

Làm cách nào để tạo trình thu thập URL để ánh xạ trang web bằng Python?

Làm cách nào để tạo trình thu thập URL để ánh xạ trang web bằng Python? .
Ghé thăm một trang web
Quét tất cả các URL duy nhất được tìm thấy trên trang web và thêm chúng vào hàng đợi
Đệ quy xử lý từng URL một cho đến khi chúng tôi sử dụng hết hàng đợi
In kết quả

Python có tốt hơn cho việc quét web không?

Python là lựa chọn tốt nhất cho bạn . Các thư viện như yêu cầu hoặc HTTPX giúp dễ dàng loại bỏ các trang web không yêu cầu JavaScript hoạt động chính xác. Python cung cấp rất nhiều ứng dụng khách HTTP dễ sử dụng. Và một khi bạn nhận được phản hồi, bạn cũng rất dễ dàng phân tích cú pháp HTML bằng BeautifulSoup chẳng hạn.

Chủ Đề