← back to Rentv Sheet Enrich Refine

web_li_scrape.py

60 lines

#!/usr/bin/env python3
"""
web_li_scrape.py — $0 HEADLESS concurrent LinkedIn-company crawler.
Fetches each company's own website (homepage + a few common pages) and extracts the
linkedin.com/company/<slug> link it publishes (footer/social bar). Real + truthful
(the company itself linked it), fast (threads), no Google/bot-block, no rate limit.
Saves data/web_li.json {domain: linkedin_company_url}.
"""
import lib, json, re, os, ssl, urllib.request
from concurrent.futures import ThreadPoolExecutor
GID=3823360
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
UA={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/537.36"}
LIC=re.compile(r'linkedin\.com/company/([A-Za-z0-9\-\_%\.]+)', re.I)
def fetch(u):
    try: return urllib.request.urlopen(urllib.request.Request(u,headers=UA),timeout=8,context=ctx).read(300000).decode("utf-8","ignore")
    except Exception: return ""
def li_of(domain):
    for path in ("","/contact","/about","/about-us","/team","/company","/contact-us"):
        for sch in ("https://","http://"):
            html=fetch(sch+domain+path)
            if not html: continue
            m=LIC.search(html)
            if m:
                slug=m.group(1).rstrip("/").split("?")[0]
                if slug.lower() not in ("unavailable","company"): return "https://www.linkedin.com/company/"+slug
        if path=="": pass
    return ""
def main():
    tok=lib.access_token()
    title={s["properties"]["sheetId"]:s["properties"]["title"] for s in lib.get_meta(tok)["sheets"]}[GID]
    rows=lib.read_tab(tok,title);hdr=rows[0]
    wi=hdr.index("Website"); qi=hdr.index("LinkedIn (Company)")
    def g(r,i): return (r[i] if i<len(r) else "").strip()
    # domains that still lack a company LinkedIn
    domains=sorted({g(rows[r],wi) for r in range(1,len(rows)) if g(rows[r],wi) and not g(rows[r],qi)})
    print(f"{len(domains)} domains to crawl for LinkedIn",flush=True)
    cache=os.path.join(os.path.dirname(__file__),"data","web_li.json")
    out=json.load(open(cache)) if os.path.exists(cache) else {}
    todo=[d for d in domains if d not in out]
    done=0
    with ThreadPoolExecutor(max_workers=40) as ex:
        for d,li in zip(todo, ex.map(li_of, todo)):
            out[d]=li; done+=1
            if done%50==0:
                json.dump(out,open(cache,"w")); found=sum(1 for v in out.values() if v)
                print(f"  {done}/{len(todo)} crawled | {found} LinkedIns",flush=True)
    json.dump(out,open(cache,"w"))
    print(f"DONE crawl: {sum(1 for v in out.values() if v)} LinkedIns / {len(out)} domains",flush=True)
    # write to master by matching each row's domain -> its LinkedIn
    rows=lib.read_tab(tok,title)
    cells=[]
    for r in range(1,len(rows)):
        dom=g(rows[r],wi); li=out.get(dom)
        if dom and li and not g(rows[r],qi):
            cells.append({"row0":r,"col0":qi,"value":'=HYPERLINK("%s")'%li,"formula":True})
    res=lib.batch_fill(tok,GID,cells)
    print(f"wrote {res.get('totalUpdatedCells',0)} company-LinkedIn cells to master",flush=True)
if __name__=="__main__": main()