Evolution Crawler Mastering Listing Sites Content Extraction

Published

evolution crawler listing sites content
Table of Contents

Modern listing sites present dynamic challenges for data extraction, where traditional crawlers often fail to capture real-time updates or API-driven content. Evolution crawlers address this gap by integrating adaptive scraping techniques—such as JavaScript rendering, proxy rotation, and behavioral simulation—to extract structured data from complex environments. This approach is critical for businesses reliant on up-to-date listings, from e-commerce platforms to real estate aggregators, where static scraping methods yield incomplete or outdated results.

The core innovation lies in the crawler’s ability to evolve alongside website architectures, detecting shifts in HTML structures, API endpoints, or anti-scraping defenses. By combining modular technical components—such as headless browser integration and session management—these systems mitigate risks like IP bans or CAPTCHAs while scaling efficiently for large datasets. Understanding their architecture and deployment strategies enables developers to build resilient scraping pipelines capable of handling even the most sophisticated listing sites.

evolution crawler listing sites content

Definition and Core Functionality of Evolution Crawler Listing Sites

Evolution crawlers represent a specialized class of web scraping tools designed to extract structured data from listing sites—platforms that dynamically generate content through JavaScript, APIs, or hybrid architectures. Unlike static crawlers, which rely on pre-rendered HTML, evolution crawlers adapt to modern web architectures by simulating real-user interactions, parsing API responses, and rendering dynamic content in real time. Their primary function is to overcome the limitations of traditional scraping methods, ensuring accurate and up-to-date data retrieval from e-commerce platforms, job boards, real estate listings, and other high-velocity content environments.

The core functionality of evolution crawlers hinges on three key mechanisms:
1. Dynamic Content Rendering – Execution of JavaScript via headless browsers or Puppeteer-like frameworks to replicate user behavior.
2. API Interception – Direct extraction of data from REST/GraphQL endpoints, bypassing client-side rendering delays.
3. Adaptive Scraping Logic – Real-time adjustments to extraction rules based on site structure changes, CAPTCHAs, or rate-limiting responses.

These tools are particularly critical for listing sites where data is not statically embedded but generated on-demand, such as infinite scroll feeds or lazy-loaded elements. By integrating browser automation, API parsing, and anti-bot evasion techniques, evolution crawlers maintain efficiency even as target sites evolve their front-end or back-end architectures.

Real-Time Data Extraction from Listing Sites

Real-time data extraction in listing sites requires crawlers to operate synchronously with content updates, which often occur through asynchronous JavaScript calls or server-side API requests. Traditional crawlers fail in such environments because they rely on pre-loaded HTML snapshots, missing dynamically injected elements like product listings, search results, or user-generated content. Evolution crawlers address this by employing:

- Event-Based Triggering – Monitoring DOM changes (e.g., `MutationObserver` in JavaScript) to detect new content loads without fixed delays.

  • API-Driven Polling – Continuously querying endpoints for updated datasets, such as pagination tokens or filtered results.
  • Session Persistence – Maintaining authenticated user sessions to access protected listings (e.g., premium job postings or private marketplaces).
  • For example, an e-commerce platform like Amazon dynamically loads product grids via API calls triggered by scroll events. A traditional crawler would only capture the initial page load, while an evolution crawler would:
    1. Simulate scroll actions to fire `IntersectionObserver` events.
    2. Parse the API response payloads (e.g., `/api/products?offset=50`) for additional items.
    3. Aggregate results into a unified dataset with metadata like load timestamps or pagination states.

    Adaptation to Dynamic Content Updates

    Dynamic content updates—such as those driven by JavaScript frameworks (React, Angular) or progressive loading techniques—pose significant challenges for static crawlers. Evolution crawlers mitigate these challenges through hybrid extraction strategies, combining:

    - Headless Browser Execution
    Tools like Puppeteer or Playwright render pages as a real browser would, executing client-side scripts to reveal hidden or lazy-loaded content. This is essential for sites relying on:

  • Infinite Scroll (e.g., LinkedIn job listings).
  • Lazy-Loaded Images/Elements (e.g., real estate property galleries).
  • Single-Page Applications (SPAs) where navigation occurs via JavaScript (e.g., Airbnb listings).
  • - API Reverse Engineering
    Many listing sites offload data fetching to backend APIs, which can be directly queried for efficiency. Evolution crawlers:

  • Identify API endpoints via network tab inspection (e.g., `fetch` or `axios` calls in browser DevTools).
  • Replicate request headers, cookies, and pagination parameters to retrieve raw data.
  • Example: A site like Zillow may expose property data via `/api/listings/v2?limit=20&offset=0`, allowing crawlers to bypass front-end delays.
  • - Dynamic XPath/CSS Selector Adjustment
    Traditional XPath selectors break when DOM structures change. Evolution crawlers use:

  • Relative Pathing (e.g., `//div[contains(@class, 'product-card')]`).
  • Attribute-Based Matching (e.g., `data-product-id` attributes).
  • Machine Learning-Assisted Parsing to auto-detect and adapt selectors when site layouts update.
  • Comparison: Traditional Crawlers vs. Evolution Crawlers

    The following table contrasts the capabilities of traditional crawlers with evolution crawlers across critical performance and adaptability metrics:
    Feature Traditional Crawlers Evolution Crawlers Key Advantage
    Speed of Data Retrieval Slow; limited by page load times (5–30 seconds per request). Fast; parallelizes API calls and headless rendering (sub-second for API-driven sites). Real-time data synchronization with minimal latency.
    Handling of CAPTCHAs Frequent failures; requires manual intervention or static bypasses. Integrated CAPTCHA solving services (e.g., 2Captcha, Anti-Captcha) or behavioral mimicry. Automated evasion of bot detection mechanisms.
    Integration with Headless Browsers None; relies on static HTML snapshots. Native support for Puppeteer, Selenium, or Playwright for dynamic rendering. Access to JavaScript-rendered content and interactive elements.
    Scalability for Large Datasets Resource-intensive; prone to IP bans or throttling. Distributed crawling with proxy rotation, session pooling, and rate-limiting controls. Sustained high-volume scraping without disruptions.
    Key Insight:
    Evolution crawlers eliminate the "last-mile problem" in web scraping—where static methods fail to capture dynamically generated or API-dependent content—by treating the target site as a programmable system rather than a static document.

    Procedure for Identifying Evolution Crawler Requirements

    Determining whether a listing site requires an evolution crawler involves analyzing its technical architecture. The following step-by-step procedure ensures accurate assessment:

    Context:
    Listing sites with any of the below characteristics necessitate evolution crawlers:

  • Heavy reliance on JavaScript for content rendering.
  • API-driven data fetching (e.g., pagination via `/api/results?page=2`).
  • Dynamic updates without full page reloads (e.g., WebSocket streams or server-sent events).
  • Step-by-Step Analysis:

    1. Page Load Behavior Analysis

  • Tool: Browser DevTools (Network, Console, and Elements tabs).
  • Actions:
  • Load the target page and observe the Network tab for:
  • XHR/Fetch requests (API calls returning JSON/XML data).
  • Beacon or EventSource connections (real-time updates).
  • Long-running scripts (indicating JavaScript-heavy rendering).
  • Check the Console tab for errors or warnings that may hint at dynamic content issues (e.g., `Failed to load resource: the server responded with a status of 404` for missing assets).
  • Red Flags:
  • Content appears after a delay (e.g., 3+ seconds post-load).
  • Elements are added via `innerHTML` or `appendChild` in the console.
  • 2. API Endpoint Detection

  • Tool: DevTools Network tab filters (`XHR` or `Doc` types).
  • Actions:
  • Sort requests by Type (XHR) and Size (large payloads often indicate data).
  • Inspect payloads for structured data (e.g., `{"items": [...], "pagination": {...}}`).
  • Note endpoints used for:
  • Initial load (e.g., `/api/products`).
  • Pagination (e.g., `/api/products?page=2`).
  • Search filters (e.g., `/api/products?category=electronics`).
  • Example:
  • A site using React may expose data via `/graphql` with queries like:

    {
    "query": "query Listings($limit: Int) { listings(limit: $limit) { id, title, price } }"
    }

    3. Dynamic Content Triggers

  • Tool: DevTools Elements tab (DOM inspection) and Performance tab.
  • Actions:
  • Technical Architecture of Evolution Crawlers for Listing Sites

    Evolution crawlers for listing sites integrate adaptive techniques to dynamically extract, normalize, and process structured data from heterogeneous web platforms. These systems prioritize scalability, resilience, and compliance with anti-scraping measures while ensuring high accuracy in parsing evolving HTML/CSS structures. The architecture is modular, allowing components like proxy rotation, user-agent spoofing, and session management to operate independently or in tandem, depending on the target site’s complexity.

    The core challenge lies in balancing performance with stealth, as listing sites frequently employ dynamic rendering (e.g., JavaScript-heavy layouts) and rate-limiting mechanisms. A well-designed crawler decomposes tasks into specialized modules—each handling distinct aspects of the scraping lifecycle—while maintaining a centralized workflow orchestrator. Below, the modular components and their interdependencies are examined, followed by a structured workflow demonstration and practical implementation insights.

    Modular Components of Evolution Crawlers

    The modularity of evolution crawlers enables targeted optimization for specific scraping challenges. Each component addresses a critical aspect of web data extraction, from circumvention of bot detection to data validation. The following modules form the backbone of a robust crawler architecture:

    Proxy Rotation Systems
    Proxy rotation mitigates IP-based blocking by distributing requests across a pool of residential, datacenter, or mobile proxies. Modern implementations employ:

  • Geographic distribution: Proxies located in regions matching the target site’s audience to reduce suspicion.
  • Session persistence: Binding a proxy to a session to maintain consistency in authenticated requests.
  • Failure detection: Automatically blacklisting proxies with high latency or HTTP 4xx/5xx responses.
  • Protocol support: Handling HTTP/HTTPS, SOCKS5, and rotating headers (e.g., `X-Forwarded-For`) to mimic organic traffic.
  • User-Agent Spoofing Mechanisms
    User-agent spoofing replicates browser fingerprints to evade detection by server-side filters. Effective strategies include:

  • Dynamic rotation: Cycling through a curated list of real-world user-agents (e.g., Chrome, Firefox, Safari) with randomized OS/device attributes.
  • Header enrichment: Adding or modifying headers like `Accept-Language`, `Referer`, or `DNT` to align with typical user behavior.
  • JavaScript challenge simulation: Emulating browser capabilities (e.g., WebGL, WebRTC) via libraries like `puppeteer-extra` or `playwright`.
  • Fingerprint randomization: Adjusting canvas rendering, WebSocket handshakes, or WebRTC leaks to avoid static signatures.
  • Session Management for Authenticated Listings
    Authenticated listings require persistent session handling to bypass login walls. Key techniques involve:

  • Cookie persistence: Storing and reusing session cookies (e.g., `PHPSESSID`, `JSESSIONID`) across requests.
  • CSRF token handling: Dynamically extracting and submitting tokens from forms or hidden inputs.
  • Multi-factor authentication (MFA) bypass: For high-security sites, using headless browsers to solve CAPTCHAs or approve 2FA prompts programmatically.
  • Session recycling: Periodically re-authenticating to renew expired sessions while minimizing detection risk.
  • Structured Crawler Workflow

    The workflow of an evolution crawler follows a linear yet adaptive pipeline, where each stage builds on the previous one’s output. Below is a high-level outline of the process, emphasizing dynamic adjustments for real-world variability:
    Initial Seed URL Selection
  • Prioritize high-authority domains or sitemaps (e.g., `/sitemap.xml`) to maximize coverage.
  • Use pre-validated seed lists or crawl public APIs (e.g., Google Custom Search) for initial targets.
  • Implement depth-first or breadth-first traversal based on site structure (e.g., breadth for wide listings, depth for nested categories).
  • Content Parsing Logic
  • Selector discovery: Combine static XPath/CSS selectors with dynamic heuristics (e.g., `//[@id='dynamic-id-']`).
  • Fallback mechanisms: Switch to alternative selectors if primary ones fail (e.g., `//div[contains(@class, 'product')]`).
  • JavaScript rendering: Use headless browsers (e.g., Puppeteer) for SPAs or sites relying on client-side rendering.
  • Data extraction: Parse structured formats (JSON-LD, Microdata) or unstructured text via NLP for semantic enrichment.
  • Data Normalization for Structured Output

  • Schema alignment: Map extracted fields to a unified schema (e.g., JSON, CSV) with validation rules (e.g., `price` must be numeric).
  • Deduplication: Remove redundant entries using fuzzy matching (e.g., Levenshtein distance for product names).
  • Geospatial normalization: Standardize location data (e.g., converting "New York, USA" to `{"lat": 40.7128, "lng": -74.0060}`).
  • Currency/unit conversion: Normalize prices to a base currency (e.g., USD) and units (e.g., kg to lbs).
  • Error Handling for Failed Requests

  • Retry policies: Exponential backoff for transient errors (e.g., 503 Service Unavailable) with jitter to avoid thundering herds.
  • Circuit breakers: Temporarily halt requests to a domain if error rates exceed thresholds (e.g., 30% failures).
  • Graceful degradation: Log failed extractions and continue with partial data (e.g., skipping images but capturing titles/descriptions).
  • Anomaly detection: Flag requests with unusual response times or headers for manual review.
  • Dynamic Selector Adjustment in Python

    Below is a Python snippet demonstrating a crawler that adapts selectors based on HTML structure changes. The example uses `BeautifulSoup` and `lxml` for parsing, with fallback logic for missing elements:

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

    class AdaptiveCrawler:
    def __init__(self, base_url):
    self.base_url = base_url
    self.session = requests.Session()
    self.session.headers.update({
    'User-Agent': random.choice([
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/90.0'
    ]),
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8'
    })
    self.selectors = {
    'title': ['h1.product-title', '//h1[@class="title"]'],
    'price': ['.price-amount', '//span[@itemprop="price"]'],
    'description': ['.product-description', '//div[contains(@class, "desc")]']
    }

    def fetch_page(self, url):
    try:
    response = self.session.get(url, timeout=10)
    response.raise_for_status()
    return response.text
    except requests.RequestException as e:
    print(f"Request failed for {url}: {e}")
    return None

    def extract_data(self, html):
    soup = BeautifulSoup(html, 'lxml')
    data = {}

    for field, (css_selector, xpath_selector) in self.selectors.items():

    Try CSS selector first

    element = soup.select_one(css_selector)
    if not element:

    Fallback to XPath (requires lxml)

    from lxml import html as lxml_html
    tree = lxml_html.fromstring(html)
    element = tree.xpath(xpath_selector)
    element = element[0] if element else None

    data[field] = element.get_text(strip=True) if element else None

    return data

    def adapt_selectors(self, html, field, new_selector):
    """Update selectors dynamically based on observed patterns."""
    soup = BeautifulSoup(html, 'lxml')
    if soup.select_one(new_selector):
    self.selectors[field][0] = new_selector # Update CSS selector
    print(f"Updated selector for {field}: {new_selector}")

    # Example usage
    crawler = AdaptiveCrawler("https://example-listing-site.com")
    html = crawler.fetch_page("https://example-listing-site.com/products")
    if html:
    data = crawler.extract_data(html)
    print("Extracted data:", data)

    Hypothetical adaptation (e.g., if '.price-amount' fails)

    crawler.adapt_selectors(html, 'price', '.new-price-class')

    Open-Source Libraries for Enhanced Crawling Capabilities

    The following tools extend the functionality of evolution crawlers by addressing specific challenges in scalability, stealth, and data processing:
    1. Scrapy (with Scrapy-Splash)
    2. evolution crawler listing sites content - Ilustrasi 2

      Challenges in Scraping Modern Listing Sites

      Modern listing sites employ sophisticated anti-scraping mechanisms to protect their data integrity, availability, and monetization models. These challenges stem from a combination of server-side defenses, client-side obfuscation, and behavioral analysis designed to distinguish automated crawlers from human users. Bypassing these measures requires an understanding of both the technical tactics deployed by listing platforms and the adaptive strategies used by crawlers to simulate organic interactions. The evolution of anti-scraping technologies—such as dynamic IP blocking, JavaScript-based challenges, and API-level restrictions—has necessitated a shift from brute-force scraping to methodical reverse-engineering and human-like automation.

      The primary obstacles include IP-based rate limiting, which dynamically blocks or throttles requests from suspicious sources, and behavioral fingerprinting, where sites analyze mouse movements, typing patterns, and session duration to detect bots. Additionally, honeypot traps (e.g., hidden form fields or clickable elements) and geolocation-based restrictions further complicate scraping efforts. These defenses are often layered, requiring crawlers to mimic not just HTTP headers but also the full spectrum of human interaction cues.

      Common Anti-Scraping Tactics and Bypass Strategies

      Listing sites deploy a diverse arsenal of anti-scraping techniques, each targeting specific vulnerabilities in automated scraping workflows. Below is a structured overview of prevalent tactics and their corresponding mitigation strategies, categorized by defense type and technical complexity.
      Anti-Scraping Tactic Mechanism Impact on Crawlers Bypass Strategy
      Cloudflare Bot Management
      • JavaScript challenges (e.g., CAPTCHAs, WebAssembly puzzles).
      • IP reputation scoring based on request patterns.
      • Behavioral analysis via browser fingerprinting (Canvas, WebGL, fonts).
      • Blocked requests or CAPTCHA prompts.
      • Increased latency due to challenge-solving delays.
      • False positives leading to unnecessary resource expenditure.
      • Use headless browsers with realistic user-agent strings and navigator overrides (e.g., navigator.webdriver = false).
      • Implement proxy rotation with residential IPs and ASN diversity.
      • Solve CAPTCHAs via third-party services (e.g., 2Captcha, Anti-Captcha) or manual intervention.
      • Replicate browser fingerprints using tools like puppeteer-extra with stealth-plugin.
      JavaScript Challenges
      • WebAssembly (WASM) obfuscation requiring execution.
      • Dynamic content rendering via IntersectionObserver or ResizeObserver.
      • Timing-based puzzles (e.g., delays before rendering content).
      • Failed requests if JavaScript is not executed.
      • Increased memory/CPU usage for emulation.
      • Race conditions in dynamic content extraction.
      • Use browser automation tools (e.g., Selenium, Playwright) with realistic execution delays.
      • Reverse-engineer WASM modules using tools like wasm2wat or Binaryen.
      • Mock JavaScript environments with JSDOM or Puppeteer for controlled rendering.
      Database-Driven Content Delivery
      • API endpoints with pagination tokens or cursor-based fetching.
      • Rate-limited or token-bucket throttling.
      • Content served via GraphQL with complex query validation.
      • Incomplete data extraction due to pagination limits.
      • Account suspension from excessive API calls.
      • High latency in GraphQL response parsing.
      • Analyze network traffic (e.g., via Chrome DevTools) to identify API patterns.
      • Implement exponential backoff for rate-limited endpoints.
      • Use GraphQL query batching and caching to minimize requests.
      • Rotate user sessions with cookies/JWT tokens to avoid IP-based bans.
      Geolocation-Based Restrictions
      • IP geoblocking via GeoIP databases (e.g., MaxMind).
      • Cookie-based region locking (e.g., __cf_geoip).
      • Device fingerprinting tied to physical location.
      • Blocked access from non-allowed regions.
      • Inconsistent data due to regional content differences.
      • Increased operational complexity for multi-region scraping.
      • Use residential proxies with verified geolocation (e.g., Luminati, Smartproxy).
      • Simulate regional user agents and time zones.
      • Leverage CDN-based proxy services (e.g., Cloudflare Workers) for dynamic IP masking.

      Simulating Human-Like Interactions in Crawler Scripts

      Modern anti-scraping systems rely heavily on behavioral analysis to distinguish bots from human users. Crawlers must replicate not only the syntactic structure of HTTP requests but also the temporal patterns, input variability, and interaction cues associated with organic browsing. This involves emulating mouse movements, touch events, and cognitive delays (e.g., reading time, decision latency) that are inherent to human behavior.

      Key strategies for human-like interaction simulation include:

    3. Randomized Timing Patterns:
    4. Crawlers should introduce stochastic delays between actions (e.g., page loads, clicks, scrolls) to mimic human hesitation. For example, a realistic delay distribution might follow a Weibull distribution with parameters tuned to observed human behavior (e.g., mean delay of 1.5–3 seconds between interactions).
      Example (Python/Puppeteer):

      import random

      import time

      def human_like_delay(min_sec=1.0, max_sec=3.0):

      delay = random.uniform(min_sec, max_sec)

      time.sleep(delay)

      # Add jitter to simulate variability

      time.sleep(random.uniform(0.1, 0.5))

    5. Mouse Movement Emulation:
    6. Anti-bot systems analyze mouse trajectories for linearity or abruptness. Crawlers should generate Bezier curves or randomized pathfinding between click points. Libraries like `pyautogui` (Python) or `robotjs` (Node.js) can simulate natural mouse motion with acceleration/deceleration profiles.
      Mouse Movement Formula (Bezier Curve):

      For a path from (x1, y1) to (x2, y2), the parametric equation is:

      Data Extraction Strategies for Structured Listing Content

      Structured data extraction from unstructured listing sites transforms raw, disparate information into standardized formats essential for analytics, machine learning, and business intelligence. Effective extraction requires a combination of parsing techniques, schema validation, and dynamic handling of modern web elements like infinite scroll and AJAX-loaded content. This section explores systematic approaches to extract, validate, and structure listing data while addressing common challenges in real-world implementations.

      Entity Recognition in Listing Data

      Entity recognition involves identifying and categorizing key components within listing content, such as product attributes, user reviews, and metadata. This process leverages natural language processing (NLP) and rule-based systems to classify unstructured text into predefined categories.

      Techniques for Entity Extraction:

    7. Named Entity Recognition (NER): Tools like spaCy or NLTK identify entities such as product names, prices, and descriptions within text. For example, extracting "iPhone 15 Pro" from a product title or "4.8/5" from a review rating.
    8. Pattern Matching with Regex: Predefined regex patterns target structured fields like dates, SKUs, or numerical values. Example:
    9. # Extract price from a string like "$999.99"
      price_pattern = r"\$\d+\.\d{2}"

      - Heuristic-Based Parsing: Rules derived from domain knowledge (e.g., "Price is always the second bolded text in a listing") guide extraction. This is useful for sites with inconsistent HTML structures.

      Example: Extracting Product Attributes from HTML

      from bs4 import BeautifulSoup
      import re

      html = """

      Brand: Samsung Model: Galaxy S23
      """

      soup = BeautifulSoup(html, 'lxml')
      attributes = {}
      for span in soup.select('.attribute-name, .attribute-value'):
      if span.has_class('attribute-name'):
      key = span.get_text(strip=True)
      elif span.has_class('attribute-value'):
      value = span.get_text(strip=True)
      attributes[key] = value
      print(attributes) # Output: {'Brand': 'Samsung', 'Model': 'Galaxy S23'}

      Schema.org Markup Extraction

      Schema.org provides standardized vocabularies for structured data, enabling search engines to interpret listing content more effectively. Extraction involves parsing JSON-LD, Microdata, or RDFa embedded in HTML.

      Methods for Schema Extraction:

    10. JSON-LD Parsing: Modern websites often embed schema data as JSON-LD scripts. Libraries like `json` or `schema-scraper` extract this directly.
    11. import json
      from bs4 import BeautifulSoup

      html = """

      """
      soup = BeautifulSoup(html, 'lxml')
      schema_data = json.loads(soup.find('script', {'type': 'application/ld+json'}).string)
      print(schema_data['name']) # Output: "Wireless Earbuds"

      - Microdata/RDFa Scraping: Use `BeautifulSoup` to locate `itemprop` or `property` attributes and map them to schema fields.

      # Extract Microdata for a Product
      product_data = {
      'name': soup.find('div', {'itemprop': 'name'}).get_text(),
      'price': soup.find('span', {'itemprop': 'price'}).get_text()
      }

      - Cross-Validation with Schema: Ensure extracted data aligns with schema definitions (e.g., `Product` must include `name`, `price`, or `offers`). Tools like Google’s Rich Results Test validate compliance.

      Hierarchical Data Mapping for Categories and Subcategories

      Listing sites organize content hierarchically (e.g., "Electronics > Smartphones > iPhone"). Mapping this structure requires parsing breadcrumb trails, category paths, or nested HTML elements.

      Approaches for Hierarchical Extraction:

    12. Breadcrumb Navigation: Extract paths like `Home > Electronics > Smartphones` from `