Back to all posts

How to Get an Up-to-Date List of Retail Clothing Stores

A list of retail clothing stores, in the sense this guide uses, is a table of store locations, one row per shop, with the name, street address, phone, website, opening hours, rating and review count, for a city or a region. It is the list an agency builds before an outreach campaign, a supplier builds before a sales trip, and an analyst builds before counting who sells what where. Product data is a different table. Prices, SKUs and stock per store live in the retailers’ own catalogs, and scraping e-commerce sites for them is a separate job.

Bought lists age between the day they are compiled and the day they are sold, so this guide builds the list from public directories instead. I pulled every result Google Maps, Yelp and Yellow Pages return for clothing stores in one city, Austin, Texas, to see how many shops each source gives up, which fields come filled, and how much the three lists overlap. Google Maps returned 543 distinct places for five queries, Yellow Pages 600 listings, Yelp stopped at 240, and only 39 store names appeared in all three.

What Each Source Returns

The three directories describe the same shops with different fields, and the gaps decide which source you start from. The counts are filled fields out of the rows each source returned for Austin.

FieldGoogle Maps (543 places)Yelp (240 listings)Yellow Pages (600 listings)
Name543240600
Street address506240587
Phone4900600
Website4800572
Opening hours4840465
Rating4862350
Review count4862350
Price band01300
Category543240600
Coordinates54300

Yelp’s search results carry a name, an address, a rating and a category and nothing to call or visit. Phone and website live on the place page, which the Yelp Place API returns for 10 credits per store, so a 240-store list costs another 2,400 credits to complete. Yellow Pages is the opposite case, with a phone number on every one of the 600 rows and a website on 572, and no ratings at all. Google Maps is the only source with coordinates, and the only one where the rating, the hours and the phone sit in the same row.

How Many Stores One City Gives Up

The Google Maps run used the Google Maps Search API with the query, a map center (ll=@30.2672,-97.7431,12z, downtown Austin at zoom 12) and start at 0, 20, 40 and so on until a page came back short. Yelp took keyword=clothing and location=Austin, TX with start in steps of 10, Yellow Pages keyword=clothing stores and the same location with page from 1 upward. Every request ran one at a time and every row was deduplicated, by placeId on Google Maps and Yelp and by listing URL on Yellow Pages.

SourceQueryPagesRows returnedUniqueCredits
Google Mapsclothing store1223920260
Google Mapswomen’s clothing store1120418355
Google Mapsmen’s clothing store1019016250
Google Mapsboutique917015445
Google Mapsthrift store815613840
Google Mapsall five together50959543250
Yelpclothing25240240250
Yellow Pagesclothing stores23635600115

Line chart of unique places returned by each of five Google Maps queries for clothing stores in Austin as result pages are added, from 20 on page one to 138 to 202 on the last page

A single query is not the city. “Clothing store” returned 202 distinct places over 12 pages, and the four narrower queries added 341 more. “Women’s clothing store” contributed 81 of them, “men’s clothing store” 57, “boutique” 117 and “thrift store” 86. 200 of the 543 places came up under two or more queries, so the overlap is real but far from total, and a list built from one query misses more than half of what Google Maps knows. The same query also moves between runs. An hour after the first pass, the identical “clothing store” request returned 179 places instead of 202, with 11 on the last page instead of 19.

Noise concentrates on the last pages. Google Maps ran dry inside Austin before it stopped returning results and filled the last pages with places elsewhere. 15 of the 543 addresses are in Colorado, Louisiana, South Carolina and Arkansas, and 37 rows have no address at all. Another 89 places (16%) carry no clothing-related category in types, among them 8 apartment buildings, second-hand stores, craft stores and non-profits that match the word “thrift”. Filtering on types before saving keeps the list to shops.

Chains inflate the count. 188 of the 543 places share a website domain with at least one other place. Goodwill Central Texas has 18 locations in the list, Target 6, Burlington, Ross, Old Navy and Academy 5 each. 352 distinct domains sit behind the 480 websites, and 64 of those URLs carry utm_ parameters that have to be stripped before two locations of the same chain can be recognized as one company. On the review side the median place has 57 reviews, 115 places have fewer than 10 and 208 have 100 or more, so a fifth of the list is either new or barely visited.

Yelp declared 240 results in searchInformation.totalResults, delivered exactly 24 pages of 10, and answered page 25 with an empty list. That is Yelp’s ceiling for a search, whatever the city holds, and the 108 sponsored listings mixed into the 24 pages are returned separately in ads and are not part of the 240. Yellow Pages declared 488 results on 17 pages and then delivered 635 rows on 22 pages (600 unique, page 23 answered HTTP 400), so its pagination block is a hint rather than a contract. Its categories are the most useful of the three for segmenting a list, with 137 men’s, 115 women’s, 58 shoe stores, 41 discount stores and 21 safety equipment and workwear suppliers that none of the Google Maps queries surfaced.

The three lists overlap less than a shared city suggests. Matching on normalized store names, Google Maps and Yelp share 96 names, Google Maps and Yellow Pages 87, Yelp and Yellow Pages 48, and 39 names appear in all three, out of 920 distinct names across the sources. Matching on phone digits, 134 of the 485 Google Maps phone numbers also appear in Yellow Pages. Adding Yellow Pages to the Google Maps list brings in 513 store names Google Maps did not have.

Scraping Clothing Stores from Google Maps

Google Maps is the source to start from because its rows are the most complete, and it comes as a no-code scraper for a one-off list and as an API for a list that gets refreshed.

With the No-Code Google Maps Scraper

The Google Maps Scraper runs from the dashboard. Sign in, open No-Code Scrapers in the left menu and pick the Google Maps card.

HasData dashboard, No-Code Scrapers list with the Google Maps card highlighted among Google Search Results, Google Maps Reviews and Amazon scrapers

The Google Maps card in No-Code Scrapers

The scraper page keeps the run history on the left, the config in the middle and a preview of the results on the right. Set the data limit, type the categories one per line under Search queries/categories (each one runs as its own query, so “clothing store”, “women’s clothing store” and “boutique” go into one run) or pick from the ready-made list, choose the country and the regions or cities under Locations, and start the run. It bills 3 credits per data row, 10 more per row with Extract emails on, so the 543 Austin places above would cost 1,629 credits this way against 250 through the API.

Google Maps Scraper page in the HasData dashboard, run history on the left, the config with data limit 100, the custom keyword retail clothing stores and the location United States, NY in the middle, and a preview table of 100 result rows with title, type, address and website on the right

Google Maps Scraper config and result preview

The preview fills as rows arrive, and the download icon above it exports the same rows the API returns, as JSON, CSV or XLSX. The preview in the screenshot also shows the noise from the API section in the no-code form, a hardware store, a drug store and a laundromat among the clothing shops for a “retail clothing stores” query.

The filtering described above, dropping rows without a clothing category and rows outside the city, is a spreadsheet filter on the type and address columns of this file.

With the Google Maps Search API in Python

The API version is for a list that has to be rebuilt every month, or for a hundred cities. Copy the API key first. It is on the API Keys page under Workspace in the dashboard.

HasData dashboard, API Keys page under Workspace, with the Default key row highlighted

API Keys under Workspace

The map center goes in ll as @latitude,longitude,zoom, and start is only accepted together with ll (without it the API answers 422 with requiredIfExists validation failed). A city name inside q alone is not a location: a request for “clothing store in Austin, TX” without ll returned a Filson store at 876 Broadway, New York, in first position. And the last pages drift, so the loop below stops when a page comes back short or adds nothing new, and the types filter drops the apartments and craft stores that share a word with the query. The Google Maps scraping guide covers the rest of the response, and the endpoint reference lists every parameter.

import csv
import time

import requests

API_KEY = "HASDATA_API_KEY"
URL = "https://api.hasdata.com/scrape/google-maps/search"
QUERY = "clothing store"
LL = "@30.2672,-97.7431,12z"  # Austin, TX: latitude, longitude, zoom
CLOTHING = ("clothing", "boutique", "thrift", "vintage", "consignment", "apparel", "fashion")


def fetch_page(start):
    """One page of up to 20 places. start only works together with ll, otherwise the API answers 422."""
    for attempt in range(4):
        response = requests.get(URL, params={"q": QUERY, "ll": LL, "start": start},
                                headers={"x-api-key": API_KEY}, timeout=120)
        if response.status_code != 429:
            response.raise_for_status()
            return response.json().get("localResults", [])
        time.sleep(5 * (attempt + 1))
    response.raise_for_status()


places = {}
start = 0
while True:
    page = fetch_page(start)
    new = [p for p in page if p.get("placeId") not in places]
    for p in new:
        places[p.get("placeId")] = p
    print(f"start={start}: {len(page)} places, {len(new)} new")
    if len(page) < 20 or not new:
        break
    start += 20
    time.sleep(1)

stores = [p for p in places.values()
          if any(word in t.lower() for t in p.get("types", []) for word in CLOTHING)]

with open("clothing_stores_austin.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["title", "type", "address", "phone", "website", "rating", "reviews",
                     "latitude", "longitude", "placeId"])
    for p in stores:
        coords = p.get("gpsCoordinates") or {}
        writer.writerow([p.get("title"), p.get("type"), p.get("address"), p.get("phone"),
                         (p.get("website") or "").split("?")[0], p.get("rating"), p.get("reviews"),
                         coords.get("latitude"), coords.get("longitude"), p.get("placeId")])

print(f"{len(places)} places returned, {len(stores)} with a clothing type, saved to clothing_stores_austin.csv")

The run that produced the CSV took 12 requests, 60 credits at 5 per request.

start=0: 20 places, 20 new
start=20: 20 places, 19 new
start=40: 20 places, 12 new
start=60: 20 places, 16 new
start=80: 20 places, 12 new
start=100: 20 places, 16 new
start=120: 20 places, 12 new
start=140: 20 places, 20 new
start=160: 20 places, 18 new
start=180: 20 places, 12 new
start=200: 20 places, 11 new
start=220: 11 places, 11 new
179 places returned, 167 with a clothing type, saved to clothing_stores_austin.csv

The .split("?")[0] on the website strips the utm_ tracking that 64 of the Austin websites carried. To cover a metro area, run the loop once per query from the table above and once per map center, and merge on placeId. A 12z center covers roughly the city proper, which is why 10 of the 543 places are in Round Rock and 8 in Bee Cave rather than hundreds.

Yelp and Yellow Pages as Second Sources

Yellow Pages is the cheaper second source and the one that adds phone numbers. Its search endpoint takes a keyword, a location and a page number, returns 30 listings per page for 5 credits, and the whole Austin list came to 23 requests. The code needs no map center and no deduplication beyond the listing URL, and it stops on the first short page or on the HTTP 400 that follows the last one.

import csv
import time

import requests

API_KEY = "HASDATA_API_KEY"
URL = "https://api.hasdata.com/scrape/yellowpages/search"

rows = {}
page = 1
while True:
    response = requests.get(URL, params={"keyword": "clothing stores", "location": "Austin, TX", "page": page},
                            headers={"x-api-key": API_KEY}, timeout=120)
    if response.status_code != 200:  # the page after the last one answers 400
        break
    results = response.json().get("organicResults", [])
    for r in results:
        rows[r["url"]] = r
    print(f"page {page}: {len(results)} rows, {len(rows)} unique so far")
    if len(results) < 30:
        break
    page += 1
    time.sleep(1)

with open("clothing_stores_yellowpages.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["title", "address", "city", "zipcode", "phone", "website", "categories"])
    for r in rows.values():
        writer.writerow([r.get("title"), r.get("address"), r.get("city"), r.get("zipcode"),
                         r.get("phone"), r.get("website"), "; ".join(r.get("categories", []))])

print(f"{len(rows)} listings saved")

The second run returned 593 listings against 600 the first time, the same kind of drift as on Google Maps and smaller. Every listing had a phone and 95% had a website, which makes Yellow Pages the source to merge in when the list feeds a call campaign rather than a map.

Yelp adds ratings and price bands for the 240 stores it is willing to show, and its first pages lean toward independent shops (141 of the 240 are tagged women’s clothing and 37 vintage or consignment). The same start pagination in steps of 10 that the Yelp scraping guide walks through ended at page 24 here, at the 240-result ceiling. Reaching a Yelp store by phone means a second request per store to the place endpoint, which is where the 10 credits per place add up: the 240 Austin places would cost 2,400 credits to complete, twenty times the whole Yellow Pages list.

Merging the Lists

Phone digits are the most reliable join key across the three sources, followed by a normalized name plus the street number. The Google Maps and Yellow Pages phones match on 134 stores and the names on 87. Yelp joins on name and street only, since its search rows have no phone. Strip everything but digits from the phone, lowercase the name and drop punctuation and the words “the”, “store” and “shop”, and keep the Google Maps placeId as the stable identifier for the merged row, because it survives renames and it is what the reviews endpoint takes if you later want the reviews behind the rating.

Matching by name alone, the 1,383 Austin rows collapse to 920 distinct store names, and the rows that fail to match are the ones worth checking by hand. Yellow Pages listings without a website, Google Maps places without an address, and chains listed under a slightly different name in each directory make up most of them.

Buying a List or Building One

A purchased list of clothing retailers costs a flat fee and arrives with an unknown compile date, and the stores that opened or closed since are the ones an outreach campaign most needs to know about. The Austin list above cost 615 credits for all three sources (250 for Google Maps, 250 for Yelp, 115 for Yellow Pages) and about an hour of wall time, and rerunning it next month costs the same again and shows exactly which rows appeared and disappeared. The emails behind the websites and the rest of a lead-generation workflow start from the merged CSV rather than from the directories.

Start with Google Maps and a handful of queries per city, filter on types, then add Yellow Pages when the list needs phone numbers on every row. Yelp is worth its credits when the rating and the price band decide which stores make the shortlist.

Valentina Skakun
Valentina Skakun
Valentina is a software engineer who builds data extraction tools before writing about them. With a strong background in Python, she also leverages her experience in JavaScript, PHP, R, and Ruby to reverse-engineer complex web architectures.If data renders in a browser, she will find a way to script its extraction.
Articles

Might Be Interesting