webmazta commited on
Commit
cb096d4
·
1 Parent(s): eea5613

fix(article): improve image extraction for lazy-loaded and JSON-LD images

Browse files
Files changed (1) hide show
  1. article_rewriter.py +79 -19
article_rewriter.py CHANGED
@@ -519,33 +519,93 @@ def scrape_article(url: str) -> dict:
519
 
520
  # --- Extract images ---
521
  images = []
522
- # Try og:image first
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  og_image = soup.find("meta", property="og:image")
524
  if og_image and og_image.get("content"):
525
  img_url = og_image["content"]
526
  if not img_url.startswith("http"):
527
  img_url = urljoin(url, img_url)
528
- images.append(img_url)
 
 
529
 
530
- # Get images from article body
531
  if content_elem:
532
  for img in content_elem.find_all("img"):
533
- src = img.get("src") or img.get("data-src") or img.get("data-lazy-src")
534
- if src and not src.startswith("data:image"):
535
- if not src.startswith("http"):
536
- src = urljoin(url, src)
537
- # Skip tiny icons, avatars, tracking pixels (Increased size thresholds to drop ad blocks/thumbnails)
538
- width = img.get("width", "")
539
- height = img.get("height", "")
540
- try:
541
- if width and int(width) < 300:
542
- continue
543
- if height and int(height) < 200:
544
- continue
545
- except ValueError:
546
- pass
547
- if src not in images:
548
- images.append(src)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
 
550
  # --- Extract embedded media (videos, social posts, audio) ---
551
  embeds = []
 
519
 
520
  # --- Extract images ---
521
  images = []
522
+
523
+ # 1. Try JSON-LD schema thumbnailUrl (most reliable for WordPress/Yoast SEO)
524
+ for script in soup.find_all("script", type="application/ld+json"):
525
+ if script.string:
526
+ try:
527
+ data = json.loads(script.string)
528
+ # Handle both single object and @graph array
529
+ if isinstance(data, dict):
530
+ if "thumbnailUrl" in data:
531
+ img_url = data["thumbnailUrl"]
532
+ if not img_url.startswith("http"):
533
+ img_url = urljoin(url, img_url)
534
+ images.append(img_url)
535
+ print(f" 🖼️ Found image in JSON-LD thumbnailUrl: {img_url}")
536
+ # Check image object
537
+ if "image" in data and isinstance(data["image"], dict) and "url" in data["image"]:
538
+ img_url = data["image"]["url"]
539
+ if not img_url.startswith("http"):
540
+ img_url = urljoin(url, img_url)
541
+ if img_url not in images:
542
+ images.append(img_url)
543
+ print(f" 🖼️ Found image in JSON-LD image.url: {img_url}")
544
+ elif isinstance(data, list):
545
+ for item in data:
546
+ if isinstance(item, dict):
547
+ if "thumbnailUrl" in item:
548
+ img_url = item["thumbnailUrl"]
549
+ if not img_url.startswith("http"):
550
+ img_url = urljoin(url, img_url)
551
+ if img_url not in images:
552
+ images.append(img_url)
553
+ print(f" 🖼️ Found image in JSON-LD graph thumbnailUrl")
554
+ if "image" in item and isinstance(item["image"], dict) and "url" in item["image"]:
555
+ img_url = item["image"]["url"]
556
+ if not img_url.startswith("http"):
557
+ img_url = urljoin(url, img_url)
558
+ if img_url not in images:
559
+ images.append(img_url)
560
+ print(f" 🖼️ Found image in JSON-LD graph image.url")
561
+ except (json.JSONDecodeError, KeyError, TypeError):
562
+ pass
563
+
564
+ # 2. Try og:image
565
  og_image = soup.find("meta", property="og:image")
566
  if og_image and og_image.get("content"):
567
  img_url = og_image["content"]
568
  if not img_url.startswith("http"):
569
  img_url = urljoin(url, img_url)
570
+ if img_url not in images:
571
+ images.append(img_url)
572
+ print(f" 🖼️ Found og:image: {img_url}")
573
 
574
+ # 3. Get images from article body (handle lazy-loading attributes)
575
  if content_elem:
576
  for img in content_elem.find_all("img"):
577
+ # Check multiple lazy-load attribute patterns (order matters: real src first, then lazy attrs)
578
+ src = None
579
+ for attr in ["src", "data-src", "data-lazy-src", "data-original", "data-srcset"]:
580
+ val = img.get(attr, "")
581
+ if attr == "data-srcset":
582
+ # Take first URL from srcset (format: "url 1024w, url2 300w")
583
+ if val:
584
+ src = val.split()[0] if val.split() else None
585
+ elif val and not val.startswith("data:image"):
586
+ src = val
587
+ break
588
+
589
+ if not src:
590
+ continue
591
+
592
+ if not src.startswith("http"):
593
+ src = urljoin(url, src)
594
+
595
+ # Skip tiny icons, avatars, tracking pixels
596
+ width = img.get("width", "")
597
+ height = img.get("height", "")
598
+ try:
599
+ if width and int(width) < 300:
600
+ continue
601
+ if height and int(height) < 200:
602
+ continue
603
+ except ValueError:
604
+ pass
605
+
606
+ if src not in images:
607
+ images.append(src)
608
+ print(f" 🖼️ Found image in article body: {src[:80]}...")
609
 
610
  # --- Extract embedded media (videos, social posts, audio) ---
611
  embeds = []