import asyncio, json, re, time, urllib.parse, urllib.request, xml.etree.ElementTree as ET
from datetime import datetime, timezone
from playwright.async_api import async_playwright
ENTITIES={
'Murprotec':'murprotec_baleares','HUMICONTROL':'humicontrol_mallorca','Humexpert':'humexpert_mallorca','ISOTEC':'isotec_mallorca','Conforthome':'conforthome_mallorca','First Mallorca':'first_mallorca','Porta Mallorquina':'porta_mallorquina','Engel & Völkers Mallorca':'engel_voelkers_mallorca','Property Care Mallorca':'property_care_mallorca'}
PLATS={'facebook':('facebook.com',re.compile(r'facebook\.com/.+/(?:posts|videos|reel|permalink\.php)',re.I)),'linkedin':('linkedin.com',re.compile(r'linkedin\.com/(?:posts|feed/update)',re.I)),'instagram':('instagram.com',re.compile(r'instagram\.com/(?:p|reel)/',re.I))}
def bing(entity, platform):
q=f'site:{PLATS[platform][0]} "{entity}"'
try:
u='https://www.bing.com/search?q='+urllib.parse.quote(q)+'&format=rss&count=25'
raw=urllib.request.urlopen(u,timeout=20).read(); root=ET.fromstring(raw); out=[]
for i in root.findall('.//item'):
url=(i.findtext('link','') or '').strip(); title=(i.findtext('title','') or '').strip(); desc=(i.findtext('description','') or '').strip(); pub=(i.findtext('pubDate','') or '').strip()
if PLATS[platform][0] in url: out.append({'url':url,'title':title,'snippet':desc,'date_raw':pub,'is_post':bool(PLATS[platform][1].search(url))})
return {'query':q,'results':out}
except Exception as e:return {'query':q,'error':str(e),'results':[]}
async def main():
rows=[]
async with async_playwright() as pw:
browser=await pw.chromium.launch(headless=True,executable_path='/snap/bin/chromium',args=['--no-sandbox'])
page=await browser.new_page()
for entity,eid in ENTITIES.items():
for platform in PLATS:
q=urllib.parse.quote('"'+entity+'"')
url='https://www.social-searcher.com/google-social-search/?q='+q
rec={'entity':entity,'watch_entity_id':eid,'platform':platform,'url':url,'retrieved_at':datetime.now(timezone.utc).isoformat(),'http':None,'results':[],'repeat':None}
try:
r=await page.goto(url,wait_until='domcontentloaded',timeout=30000); rec['http']=r.status
await page.wait_for_timeout(1800)
frame=page.frame(name=None)
# select the platform iframe by URL, then inspect its rendered Google CSE DOM
fs=[f for f in page.frames if f.url and platform+'cse.html' in f.url]
if fs:
f=fs[0]
links=await f.locator('a').evaluate_all('(els)=>els.map(a=>({url:a.href,text:(a.innerText||a.textContent||"").trim()})).filter(x=>x.url)')
body=(await f.locator('body').inner_text())[:8000]
for x in links:
if PLATS[platform][0] in x['url']:
rec['results'].append({'url':x['url'],'text':x['text'],'is_post':bool(PLATS[platform][1].search(x['url']))})
rec['body_excerpt']=body
# repeat same query once, measuring stable URL intersection
await page.reload(wait_until='domcontentloaded',timeout=30000); await page.wait_for_timeout(1800)
fs2=[f for f in page.frames if f.url and platform+'cse.html' in f.url]
urls2=set()
if fs2:
links2=await fs2[0].locator('a').evaluate_all('(els)=>els.map(a=>a.href).filter(Boolean)')
urls2={u for u in links2 if PLATS[platform][0] in u and PLATS[platform][1].search(u)}
urls1={x['url'] for x in rec['results'] if x['is_post']}
rec['repeat']={'second_post_count':len(urls2),'intersection':len(urls1 & urls2),'stable':bool(urls1 and urls1==urls2)}
except Exception as e: rec['error']=str(e)
rows.append(rec); print(entity,platform,rec['http'],len(rec['results']),rec.get('repeat'))
await browser.close()
# Bing comparison, bounded and no credentials/cookies
for entity,eid in ENTITIES.items():
for platform in PLATS:
rows.append({'entity':entity,'watch_entity_id':eid,'platform':platform,'source':'bing_rss','retrieved_at':datetime.now(timezone.utc).isoformat(),'bing':bing(entity,platform)})
time.sleep(.25)
json.dump(rows,open('/tmp/p7_discovery.json','w'),ensure_ascii=False,indent=2)
print('WROTE',len(rows))
asyncio.run(main())