What this guide answers
Pulling image URLs out of a page is one of the most common scraping tasks, and BeautifulSoup makes it almost trivial in the easy case and surprisingly fiddly in the real one. This handbook covers both. We start with the one-liner everyone reaches for, then deal with the messy reality of modern pages: lazy loading, responsive srcset, relative URLs and JavaScript-injected images. By the end you will have a robust pattern that returns clean, absolute image URLs from almost any page.
We also place this within a complete pipeline, because extracting the src is only useful if you can fetch the pages and the images reliably, which is where proxies enter the picture.
The basic idea
An image on a web page is an <img> tag, and its address lives in the src attribute. BeautifulSoup treats a tag like a dictionary of attributes, so once you have the tag, reading src is direct. The work is mostly about reliably locating the right img tags and coping with the variations sites use to store the actual URL.
Setting up: parse the HTML
Start by parsing the page into a soup object. In practice you fetch the HTML first, then hand it to BeautifulSoup:
import requests
from bs4 import BeautifulSoup
resp = requests.get("https://example.com", timeout=20)
soup = BeautifulSoup(resp.text, "html.parser")
Everything that follows operates on this soup.
Getting the src of a single image
Find the first matching img and read its src. The safest way to read an attribute is the get method, which returns None rather than raising if the attribute is absent:
img = soup.find("img")
url = img.get("src") # safe: None if missing
# or, if you are certain it exists:
url = img["src"] # raises KeyError if missing
Prefer get in scraping code, because real pages are inconsistent and you do not want one stray tag to crash a long-running job.
Getting the src of every image on a page
Use find_all to collect all images, then loop, skipping any without a src:
urls = []
for img in soup.find_all("img"):
src = img.get("src")
if src:
urls.append(src)
This gives you a clean list of image URLs. A list comprehension does the same in one line, but the explicit loop is easier to extend when you add the lazy-load handling below.
Targeting specific images with selectors
Often you only want images inside a particular section, such as product photos rather than icons. CSS selectors via select make that precise:
for img in soup.select(".product-gallery img"):
print(img.get("src"))
Scoping your selection avoids logos, sprites and tracking pixels, and produces a far cleaner result than grabbing every img on the page.
The lazy-loading problem
Here is where naive scrapers fail. To speed up page loads, many sites put a tiny placeholder or a transparent pixel in src and store the real image URL in data-src, data-original, data-lazy or a srcset attribute. If your scraper only reads src, you collect placeholders instead of pictures. The fix is to check the common lazy-load attributes in order and fall back to src last:
def best_src(img):
for attr in ("data-src", "data-original", "data-lazy", "src"):
val = img.get(attr)
if val:
return val
return None
If your image scraper suddenly returns identical tiny URLs or base64 placeholders, lazy loading is almost always the cause. Inspect one img tag in the browser's developer tools, note which attribute holds the real URL, and add it to your fallback list.
Handling srcset for responsive images
Responsive images use srcset, a comma-separated list of URLs with width descriptors. To pick one, split on commas, take the URL portion of each entry, and choose the largest or the first as your needs dictate. It is a little more parsing, but it is the only way to get the high-resolution version when a site serves several sizes.
Turning relative URLs into absolute ones
A src is frequently relative, such as /images/photo.jpg, which is useless on its own. Resolve it against the page URL with urljoin:
from urllib.parse import urljoin
absolute = urljoin("https://example.com/page", img.get("src"))
urljoin correctly handles relative, root-relative and protocol-relative paths, so you always end up with a complete, downloadable URL. Always run extracted srcs through it before storing or fetching them.
When images need JavaScript
BeautifulSoup only sees the HTML you fetched. If a page builds its gallery with JavaScript after load, those images are not in the source and no amount of attribute hunting will find them. The solution is to render the page first with a headless browser such as Selenium or Playwright, wait for the images, then pass the rendered HTML to BeautifulSoup for the familiar extraction step.
Putting it together
A robust extractor combines the pieces: scope the selection, prefer lazy-load attributes, parse srcset when present, and absolutise every URL:
from urllib.parse import urljoin
page = "https://example.com/gallery"
images = []
for img in soup.select("img"):
src = best_src(img)
if src:
images.append(urljoin(page, src))
This pattern survives most of the variations real pages throw at it.
Who needs this
E-commerce teams cataloguing product photos, researchers building image datasets, SEO specialists auditing alt text and image coverage, and automation builders mirroring media all extract image src the same way. The techniques scale from one page to an entire catalogue.
A reliability checklist
- Am I using
getrather than bracket access to avoid crashes? - Have I checked data-src and srcset for lazy-loaded images?
- Did I resolve relative URLs with
urljoin? - Is my selector scoped to the images I actually want?
- Do the images need a headless browser to appear?
- Have I added a proxy before fetching at volume?
Common mistakes to avoid
Watch for reading only src and collecting placeholders, using bracket access that crashes on a missing attribute, storing relative URLs that cannot be downloaded later, grabbing every img including logos and tracking pixels, and assuming JavaScript-built images are present in the static HTML. Each has a clean fix once you recognise the symptom.
Why proxies matter for image scraping
Extracting the src costs nothing, but acting on it does. Crawling many pages to gather URLs, and then downloading the images themselves, means a lot of requests, and a single IP hitting a site that hard will be rate limited or blocked fast. Rotating proxies spread those requests across many addresses, letting you collect and download images at scale without tripping a target's defences.
Recommended proxy providers
For image work the proxy choice shapes both your success rate and your bandwidth bill. We weigh the options on value and fit, not marketing.
Beyond our featured value pick, a few established names merit a fair comparison:
- Bright Data offers a large network and detailed targeting, suited to big teams that need breadth at a premium.
- Smartproxy keeps setup approachable with clear docs, a comfortable choice as image jobs grow.
- Oxylabs handles heavy, high-volume downloading with broad coverage and strong support when reliability leads.
Whichever you shortlist, trial each on your real targets and weigh success rate, especially on image-heavy pages, against cost.
How to get started
Install BeautifulSoup and requests with pip, fetch a sample gallery page, and print the src of every img. Add the lazy-load fallback and urljoin, confirm the URLs actually download, then attach a proxy and a polite delay before scaling to a full catalogue.
Key takeaways
Getting an img src with BeautifulSoup is a one-liner in theory and a small toolkit in practice: read with get, loop with find_all, prefer lazy-load attributes and srcset, and absolutise with urljoin. Remember that BeautifulSoup parses but does not render or fetch, so reach for a headless browser when JavaScript builds the images, and pair the whole pipeline with a value-focused proxy provider to scrape and download images reliably and affordably.
Related proxy guides
Frequently asked questions
Questions or a correction? Email info@proxyranked.com. Always confirm a provider's exact package, proxy type and locations before ordering.