Zumper Scraper - Rentals, Prices, Agents & 60+ Fields avatar

Zumper Scraper - Rentals, Prices, Agents & 60+ Fields

Pricing

from $1.20 / 1,000 listing results

Go to Apify Store
Zumper Scraper - Rentals, Prices, Agents & 60+ Fields

Zumper Scraper - Rentals, Prices, Agents & 60+ Fields

Scrape Zumper.com rental listings: apartments, houses, condos and rooms. Pulls 60+ fields per listing, including monthly rent, beds/baths, square footage, decoded amenities, agent name and phone, ratings, availability and full-resolution photos. Search by location with filters, or paste Zumper URLs.

Pricing

from $1.20 / 1,000 listing results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

3

Total users

1

Monthly active users

8 days ago

Last modified

Features

Limited permissions

Share

Zumper Scraper: Rental Listings, Rent and Availability Data

Zumper Scraper turns Zumper (zumper.com), a US rental listing site, into a clean apartment, house, condo and room dataset. Search by location with rich filters, or paste your own Zumper search, building or listing links. Every record carries monthly rent, beds/baths, square footage, decoded amenities, agent name and phone, ratings, availability and full resolution photo URLs. It also monitors listings for changes across runs (NEW, UPDATED, UNCHANGED, REAPPEARED, EXPIRED), resumes an interrupted pull, and pipes results into your apps through MCP connectors.

Why This Scraper?

  • 60+ fields per listing, core data straight from the results page. Price, beds/baths, square footage, amenities, agent name, phone, rating and photos come back without turning on detail fetching.
  • Amenities decoded to plain text. Amenities and building features come back as readable labels ("In-Unit Laundry", "Fitness Center", "Garage Parking"), not opaque numeric codes.
  • Full photo URLs, not just IDs. Every image is returned as a ready to open URL.
  • Two ways to find data. Search by location and filters, or paste specific search, building or listing URLs (multiple URLs supported).
  • Availability monitoring. Scheduled runs classify every listing as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with per campaign state you can name or share.
  • Resume long pulls. Continue an interrupted or unbounded walk across separate runs without paying twice for what was already collected.
  • Predictable cost. A small default result cap, per record output, and an optional per listing detail surcharge only when you turn detail fetching on.

Use Cases

  • Rent trend and market research: track rent ranges, price drops and new listings across neighborhoods and cities over time.
  • Apartment hunters and relocation services: pull filtered listings (price, beds, pets) for a target city into your own site or alert tool.
  • PropTech and rental aggregators: build a secondary Zumper sourced feed with agent contacts and photos, without visiting the site by hand.
  • Investors and property managers: monitor competing buildings' pricing and availability in a submarket.
  • Lead generation for movers and brokers: watch specific buildings or listings for status changes (NEW, price change, EXPIRED) and reach out through the agent contact on the record.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"64606115"
urlhttps://www.zumper.com/apartment-buildings/p000000/sample-building-city-st
title"1A with Balcony"
buildingName"Sample Apartments"
propertyCategory"apartments" (also "houses", "condos", "rooms")
address"123 Sample St"
city / state / zipcode"San Francisco" / "CA" / "94103"
neighborhood"SOMA"
latitude / longitude37.7700 / -122.4000
minPrice / maxPrice4035 / 6360 (monthly rent, USD; the same names work as rent filters in both modes)
previousPrice4200 (set when the rent dropped)
minBedrooms / maxBedrooms0 / 2 (0 = studio)
minBathrooms / maxBathrooms1 / 2
minSquareFeet / maxSquareFeet509 / 980
dateAvailable"2026-01-15"
rating / externalRating9.3 / 4.1
unitAmenities["In-Unit Laundry", "Dishwasher", "Air Conditioning"]
buildingAmenities["Fitness Center", "Garage Parking", "Roof Deck"]
agentName / brokerageName / phone"Sample Mgmt" / "Sample Brokerage" / "(415) 555-0100"
primaryImage / images / imageCounthttps://img.zumpercdn.com/000000000/1280x960 / [...] / 10
shortDescription"Bright corner unit with balcony."
scrapedAt"2026-01-01T00:00:00.000Z"
changeType"NEW" (only present when incrementalMode is on, see below)
changedFields, firstSeenAt, lastSeenAtIncremental mode only

With Fetch detail pages on, a record is additionally enriched with the full description, year built and floors, amenities grouped by category, pet policy, parking, deposits, fees, specials, income restrictions, lease terms, office hours, per unit and per floorplan availability, full agent and property manager contacts, a category by category rating breakdown, and Zumper's editorial resident feedback summary. Zumper does not publish individual resident reviews (per comment text with an author and date): the rating and feedback fields above are all of the review data that Zumper itself exposes, so userReviews is always present as an empty array rather than omitted.

How to Use

  1. Pick a mode: search (build a URL from the fields below) or url (paste your own Zumper URLs).
  2. In search mode, add locations and any filters (price, beds, baths, pets); switch on Fetch detail pages if you need the fields above.
  3. Set Max listings (and optionally Max pages) to control run size and cost, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search apartments in a city:

{
"mode": "search",
"locations": ["San Francisco, CA"],
"propertyCategory": "apartments",
"maxListings": 20
}

Search houses with filters, cheapest first, full detail:

{
"mode": "search",
"locations": ["Austin, TX"],
"propertyCategory": "houses",
"minPrice": 1500,
"maxPrice": 6000,
"minBedrooms": 2,
"petsAllowed": ["dogs"],
"sortBy": "price-asc",
"fetchDetails": true,
"maxListings": 50
}

Paste your own Zumper URLs (search results, building or listing):

{
"mode": "url",
"urls": [
"https://www.zumper.com/apartments-for-rent/new-york-ny",
"https://www.zumper.com/apartment-buildings/p000000/sample-building-san-francisco-ca"
],
"fetchDetails": true
}

URL mode shapes. Each shape below was loaded in a real run and returned listings; the urls field is prefilled with one example of each search shape. The field's default equals the prefill, so a URL-mode run with no links scrapes those example searches (capped by Max listings); paste your own links to scrape something else. ZIP-slug links (/apartments-for-rent/94110-san-francisco-ca), state-level links (/apartments-for-rent/ca) and some neighborhood slugs such as /mission-district load but carry no listings: use the city and ZIP form or a neighborhood slug that lists results.

ShapeExample path
City search/apartments-for-rent/san-francisco-ca
Category search (houses, condos, rooms)/houses-for-rent/austin-tx
Paginated search (starts at that page)/apartments-for-rent/san-francisco-ca?page=2
Filtered or sorted search/apartments-for-rent/san-francisco-ca?price-max=3000&bed-min=1&sort=newest
Neighborhood search (use the neighborhood slug shown on Zumper)/apartments-for-rent/san-francisco-ca/soma
City and ZIP search/apartments-for-rent/san-francisco-ca/94110
Building or listing page/apartment-buildings/p13179/<slug>, /apartment-buildings/<id>/<slug>, /address/<street-city-zip-usa>

Facets in a pasted URL (price-min, price-max, bed-min, bed-max, bath-min, pets, sort) win over the run level filter fields. The tracked scope for recurring updates includes the full pasted URL. A link that is not a recognised Zumper search or building page is logged with a warning and still attempted.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/zumper-scraper").call(run_input={"mode": "search", "locations": ["San Francisco, CA"], "maxListings": 20})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
print(listing["title"], listing["minPrice"], listing["city"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/zumper-scraper').call({
mode: 'search', locations: ['San Francisco, CA'], maxListings: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Output views

The dataset has three views in the Console: Listings Overview (the main fields of every record), Listing Details (Fetch details only) (description, amenities, units, contacts and ratings breakdown) and Changes (Incremental mode) (changeType, changedFields, detectedAt, priceChangeAmount, priceChangePercent, leadScore and stateKey for monitoring runs). The incremental fields detectedAt, leadScore, priceChangeAmount, priceChangePercent and stateKey (the saved-search identity: your State key as manual:<text>, or the automatic auto:<hash> key) are added only to incremental rows and never count as a change themselves.

Field availability and the dedup key

Field availability can vary by region, listing type and Zumper interface changes; use id as the idempotency / dedup key across runs. Fetch detail pages defaults to off because the results page already carries the large majority of fields; turn it on when you need the full description, ratings breakdown, units or agent records.

Both modes, one filter set

minPrice, maxPrice, minBedrooms, maxBedrooms, minBathrooms and sortBy apply in both modes: in URL mode they narrow and reorder the results of a pasted search results URL after they are read, so a pasted URL can return fewer listings than the page itself shows. A pasted building or listing URL is saved as is and is never narrowed by them; each such URL returns exactly one record (its own fields, plus units / floorplanListings for unit level detail when Fetch detail pages is on). locations, propertyCategory and petsAllowed are search only: they shape the search URL this actor builds and are not re applied to results that come back.

How Max listings shapes a run

With one location or URL, Max listings simply stops the run once that many records have been pushed. With several locations, the budget is split into an equal share per location (rounded up), and each location stops at its own share, its own last page, or the site's own end of results, whichever comes first.

Known limitation: on each results page, the number of listings attempted is sized from the remaining budget BEFORE incremental mode classifies them, and every listing on the page is marked as handled either way. If already tracked, unchanged listings sort ahead of new or changed ones on a page, the attempts can be spent on suppressed rows and the new or changed listings further down that page are silently skipped for this run (the walk moves on to the next page, or ends, and never comes back), even though the run still finishes normally. This only bites in incrementalMode with a finite Max listings (the default is 20); a non incremental run is not affected, since nothing is suppressed. Skipped new listings show up on a later run instead. Workaround: set Max listings to 0 (or well above the size of the search) on recurring monitoring runs.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips everything already collected there, so you don't pay twice. A migration or Resurrect of a failed run also checkpoints automatically and picks back up in the same run without input.
  • Incremental mode (incrementalMode) is for scheduled runs over the same search. Each record is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED or EXPIRED (only after a run that scanned the whole tracked search, and only with emitExpired). stateKey names or shares the stored state; leave it empty to derive one automatically from the location, category, filters and fetchDetails, so different searches never mix state with each other. UPDATED means any output field changed except scrapedAt and the fields in ignoreFieldsForChanges; the order of the amenity lists never counts (the photo order does, because the first photo is the cover image). Saved state is re-compared under the current ignore list, so changing it never reports every listing as UPDATED.
  • Ignore fields (ignoreFieldsForChanges, last field of this section): field names, or dotted paths such as units.status that reach inside every array element, that must not count as a change. They stay in the output.
  • Price and availability monitoring (all default off): changeScope=priceAndStock compares only the rent fields, dateAvailable and each unit's id, price and availability keys; compactUpdates returns UPDATED and EXPIRED rows as slim records with priceChanges and variantChanges (old and new). A compact row carries no detail page data, so it is not charged the detail enrichment event; every other row is billed as before. expireAfterMissedScans (default 1) holds a listing until it is missing from that many consecutive complete scans. EXPIRED means the listing dropped out of the results, not that it is unavailable.
  • Known limitation: emitExpired rows are not subject to Max listings. Once a run qualifies to detect EXPIRED listings, every tracked listing found gone is pushed and billed, however many that is; Max listings only bounds the live NEW/UPDATED/REAPPEARED side of the same run. Budget for this on a long running campaign, or keep emitExpired off unless you want the full expired batch every time it fires.
  • Known limitation: searching several overlapping or adjoining locations in one run can end a location's walk early. If two locations return some of the same listings (a shared boundary, a duplicate feed entry), a later location can reach a results page made up entirely of listings an earlier location already returned; its walk then reads that page as a repeat and stops, even though that location has more, never seen pages left. This both drops real listings from the run and, with emitExpired on, can mark a still live listing on the unscanned pages as EXPIRED. Workaround: avoid pairing locations whose inventories are likely to overlap in one run, or run them as separate campaigns (separate stateKey).
  • Known limitation: a stateKey shared or reused across a differently scoped run can produce false EXPIRED rows. This includes deliberately sharing one stateKey between two different location, URL or search mode filter setups (in URL mode the rent, bedroom and bathroom filters are applied after reading and do not trigger this), and pasting a search URL that starts mid pagination (for example a copied ?page=3 link) under a stateKey that previously tracked the whole search: the listings that a differently scoped or later starting run never re-visits are not actually gone, but a complete-looking scan of the narrower scope will still mark them EXPIRED. Workaround: let stateKey stay empty (automatic, per scope) unless you specifically want to share state, and always paste page 1 links for a tracked campaign.
  • A monitoring campaign that is bootstrapped from a resumeFromRunId dataset (its first incremental run only; combining the two after a baseline exists fails fast with a clear message) does not suppress the listings it bootstrapped: on the next scheduled run each of them comes back as UPDATED, with every field listed in changedFields, instead of UNCHANGED. Those rows are pushed and billed like any other row (plus the detail surcharge when Fetch detail pages is on), even though you already paid for them in the resumed run; billing does not depend on the change type, so it costs the same as if they had come back NEW. After that one run they are tracked normally.
  • If the run is interrupted by a platform migration or resurrected while incrementalMode is on, listings pushed before the interruption are protected from being re-pushed or re-billed in that same run, but the automatic checkpoint saves only which listings were already handled, not their incremental classification. Their change is therefore never saved to the baseline, and on the next scheduled run they are reported (and billed) again: a listing that was NEW reads NEW again, and one that was UPDATED or REAPPEARED reads the same way again, instead of UNCHANGED.
  • Known limitation: a results page that loads but carries no readable listing data is treated as the end of that search. If Zumper changes its page layout or serves an unrecognized placeholder page, the walk stops as if the search had run out, and with emitExpired on, every tracked listing from that page onward is marked (and billed) EXPIRED even though it is still live. If a monitoring run suddenly returns a large EXPIRED batch, check whether the same search still shows those listings on the site before acting on it; they come back as REAPPEARED on the next healthy run.

Send results into your apps (MCP connectors)

Optionally pipe each scraped listing into the tools you already use (Notion, Linear, Airtable, or any Model Context Protocol server) by selecting an authorized connector in the input. Each connector receives a condensed, human readable summary per item; the complete record always stays in the dataset.

  1. Authorize a connector once under Apify -> Settings -> Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the Pipe results into your apps input field. (If the picker is empty, you have not authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (build a URL from the fields below) or url (use your own URLs).
locationsarrayNew York, NY when emptySearch mode only. City, state or neighborhood, multiple supported. A bare ZIP code is not a Zumper search page (a warning is logged); use a city and ZIP link in URL mode instead.
propertyCategorystringapartmentsSearch mode only for the category to browse. In URL mode it only labels records when the pasted URL names no category itself.
petsAllowedarray(none)Search mode only. Ask Zumper for listings that allow the selected pets (dogs, cats).
urlsarraysampleURL mode only. Zumper search, building or listing links.
minPriceinteger(none)Both modes. Minimum monthly rent (USD).
maxPriceinteger(none)Both modes. Maximum monthly rent (USD).
minBedroomsinteger(none)Both modes. Minimum bedrooms (0 = studio).
maxBedroomsinteger(none)Both modes. Maximum bedrooms.
minBathroomsinteger(none)Both modes. Minimum bathrooms.
sortBystringdefaultBoth modes. default, price-asc, price-desc, newest, rating-desc.
fetchDetailsbooleanfalseAdd full description, ratings breakdown, units/floorplans, agent contacts and more.
maxPagesinteger(none, unlimited)Result pages walked per location or URL. Leave empty to walk every page (still bounded by Max listings).
maxListingsinteger20Stop after this many listings across all searches (0 = unlimited).
resumeFromRunIdstring(none)Continue one interrupted run, or bootstrap the first run of a new incremental campaign.
incrementalModebooleanfalseTurn on for recurring monitoring of the SAME search.
stateKeystring(none)Names or shares an incremental mode state.
emitUnchangedbooleanfalseAlso return (and bill) unchanged listings.
emitExpiredbooleanfalseAlso return (and bill) listings tracked before but no longer found.
expireAfterMissedScansinteger1Consecutive complete scans a listing must be missing before it is EXPIRED.
changeScopestringallall or priceAndStock (only rent, availability date and per-unit price/availability count as a change).
compactUpdatesbooleanfalseSlim UPDATED and EXPIRED rows with priceChanges and variantChanges.
ignoreFieldsForChangesarray(none)Field names or dotted paths left out of change detection (incremental mode only).
proxyobjectResidentialProxy and connection settings; managed automatically, or supply your own proxy URLs.
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which records are created.
maxNotifyListingsinteger50Cap on items written to each connector per run.

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": "64606115",
"url": "https://www.zumper.com/apartment-buildings/p000000/sample-building-city-st",
"padmapperUrl": null,
"title": "1A with Balcony",
"buildingName": "Sample Apartments",
"propertyCategory": "apartments",
"isBuilding": true,
"address": "123 Sample St",
"city": "San Francisco",
"state": "CA",
"zipcode": "94103",
"neighborhood": "SOMA",
"latitude": 37.77,
"longitude": -122.4,
"minPrice": 4035,
"maxPrice": 6360,
"previousPrice": 4200,
"currency": "USD",
"minBedrooms": 0,
"maxBedrooms": 2,
"minBathrooms": 1,
"maxBathrooms": 2,
"minSquareFeet": 509,
"maxSquareFeet": 980,
"dateAvailable": "2026-01-15",
"listedOn": "2025-12-01T00:00:00.000Z",
"modifiedOn": "2026-01-01T00:00:00.000Z",
"unitAmenities": ["In-Unit Laundry", "Dishwasher", "Air Conditioning"],
"buildingAmenities": ["Fitness Center", "Garage Parking", "Roof Deck"],
"shortDescription": "Bright corner unit with balcony.",
"rating": 9.3,
"externalRating": 4.1,
"agentName": "Sample Mgmt",
"brokerageName": "Sample Brokerage",
"phone": "(415) 555-0100",
"primaryImage": "https://img.zumpercdn.com/000000000/1280x960",
"images": ["https://img.zumpercdn.com/000000000/1280x960"],
"imageCount": 1,
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Plan Requirement

Zumper serves a "Client Challenge" interstitial to plain HTTP clients instead of listings. This actor loads pages in a real browser, which clears it, and in testing both an Apify residential connection and an Apify datacenter connection loaded the search pages. The Proxy & connection field defaults to Apify Residential, which is included on paid Apify plans, and the actor rotates to the next available connection when one is refused. Apify free plans do not include Apify proxies. On a free plan the log shows "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and the run then either returns the rows the backup connection could read or fails. If the backup connection could not load the pages, the run fails with "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Zumper pages." If every connection is refused for the account, it fails with "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." Any other run in which no page loads fails with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search." To run on a free plan, upgrade at https://apify.com/pricing or supply your own proxy URLs in the Proxy & connection field.

FAQ

How much does it cost?

Billing has three events: the Apify actor start event once per run, apify-default-dataset-item once per listing record pushed to the dataset, and detail-enrichment once per listing that was enriched when Fetch detail pages is on (not for slim compact update rows, and not for unchanged listings that incremental mode suppresses). Nothing is charged for pages that fail or listings that are not returned. The Pricing tab shows the current rates. Use Max listings to cap the cost of a run.

This actor collects only publicly available rental listing data. You are responsible for how you use it: follow Zumper's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts (rent, beds, baths, address) are generally not subject to copyright, but photos and agent supplied descriptions may carry third party rights.

Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared listings (plus expired ones when emitExpired is on), and unchanged ones are not billed unless you turn on emitUnchanged.

Why did my run return fewer listings than I expected?

With several locations, Max listings is split into an equal share per location, and each stops at its share or its last page, whichever comes first. Filters (price, beds, baths, pets) also narrow the result set. On a recurring, incremental run over a location with a lot of steady state (unchanged) inventory, the per page budget can also be used up on suppressed rows before reaching new ones on the same page (see "How Max listings shapes a run" above). Raise Max listings, or set it to 0, to collect more.

Why did my run finish with an empty dataset?

It depends on whether any page loaded. If pages loaded but nothing matched your search or filters (or incremental mode suppressed every unchanged listing), the run finishes SUCCEEDED with an empty dataset, the status message "No listings returned" and nothing billed. A results page that loads but carries no readable listing data is treated as the end of that search. If no page could be loaded at all (the site or the connection refused every request), the run ends FAILED with one of the connection messages quoted under Plan Requirement ("The proxy connection was refused for this account...", "No results: residential proxies are unavailable on this run...", or "Could not load any results page..."), so a refused connection is never reported as an empty success. Try a different location, widen the filters, or run it again in a few minutes; supplying your own proxy URLs in the Proxy & connection field can also help. If only some locations or listings could be read, you still get what was found.

Does emitExpired respect Max listings?

No. Max listings caps the live NEW/UPDATED/REAPPEARED side of a run; once a run qualifies to detect EXPIRED listings, every tracked listing found gone is pushed regardless of Max listings. See the known limitation under "Resume and recurring updates" above.

Can EXPIRED ever be wrong?

In specific cases, yes: overlapping or adjoining locations searched together in one run, or a stateKey that is shared or reused across a differently scoped or differently starting run (including a pasted URL that starts mid pagination), or a results page that loads without readable listing data, can make a still live listing look unscanned this run and get marked EXPIRED. See the known limitations under "Resume and recurring updates" above for the exact triggers and the workaround.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the Pipe results into your apps field to push results into Notion, Linear or Airtable.

πŸ”— Want more real estate data?

Pair this actor with these related scrapers from the same team:

🧩 Padmapper Scraper
Scrape Padmapper apartment rentals across US/CA cities: rent, beds, baths, sqft...
🏠 Rent.com Scraper
Extract rental listings from rent.com. Get comprehensive data including monthly rent...
🏠 Trulia Scraper
Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds...
🏠 QuintoAndar Scraper
Scrape QuintoAndar property listings across Brazil with 80+ fields, including rent, sale...
🏠 StreetEasy Scraper
Scrape StreetEasy sales, rentals and in-contract listings across NYC and Jersey City...
🏠 Realtor.ca
Scrape Realtor.ca property listings with prices, descriptions, images, agent and...

πŸ‘‰ Browse all abotapi scrapers

πŸ’¬ Support

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • πŸ› οΈ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.
⌂ 捒网址