使用Selenium抓取LinkedIn职位信息出现重复/空结果求助
LinkedIn职位抓取重复/空结果问题排查与修复
问题描述
我正在做一个练习项目,用Selenium抓取LinkedIn上的澳洲数据分析师职位信息。思路是先获取职位卡片元素和Job ID,逐个点击卡片加载详情,再提取职位名称、公司名等信息。但运行时出现重复或空结果:Job ID能正常更新,但职位详情会随机重复。相关代码如下:
import time import pandas as pd from selenium import webdriver from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException login_page_link = 'https://www.linkedin.com/login' search_page_link = 'https://www.linkedin.com/jobs/search/?geoId=101452733&keywords=data%20analyst&location=Australia&refresh=true' job_list_item_class = 'jobs-search-results__list-item' job_title_class = 'jobs-unified-top-card__job-title' company_name_class = 'jobs-unified-top-card__company-name' def get_browser_driver(): browser = webdriver.Chrome() # maximise browser window browser.maximize_window() return browser def login_to_linkedin(browser): browser.get(login_page_link) # enter login credentials browser.find_element(by=By.ID, value=username_id).send_keys("username@mail.com") browser.find_element(by=By.ID, value=password_id).send_keys("pwd") login_btn = browser.find_element(by=By.XPATH, value=login_btn_xpath) # attempt login login_btn.click() # wait till new page is loaded time.sleep(2) def get_job_post_data(browser): # list to store job posts job_posts = [] # get the search results list job_cards = browser.find_elements(by=By.CLASS_NAME, value=job_list_item_class) for job_card in job_cards: job_id = job_card.get_attribute('data-occludable-job-id') # dict to store each job post job_dict = {} # scroll job post into view browser.execute_script("arguments[0].scrollIntoView();", job_card) # click to load each job post job_card.click() time.sleep(5) # get elements from job post by css selector job_dict['Job ID'] = job_id job_dict['Job title'] = get_element_text_by_classname(browser, job_title_class) job_dict['Company name'] = get_element_text_by_classname(browser, company_name_class) job_posts.append(job_dict) return job_posts def get_element_text_by_classname(browser, class_name): return browser.find_element(by=By.CLASS_NAME, value=class_name).text browser = get_browser_driver() login_to_linkedin(browser) load_search_results_page(browser) jobs_list = get_job_post_data(browser) jobs_df = pd.DataFrame(all_jobs)
问题原因分析
- 失效元素引用(Stale Element Reference):LinkedIn职位列表是动态渲染的,页面滚动或点击卡片后,初始获取的
job_cards列表元素会失效,后续操作可能触发旧元素,导致抓取到重复内容。 - 固定等待不可靠:
time.sleep(5)的固定等待逻辑,要么因加载未完成提前提取数据,要么因页面更新导致元素状态变化,无法保证数据准确性。 - 全局定位歧义:用
browser.find_element全局查找详情元素时,页面可能残留之前加载的卡片内容,导致提取到旧的职位信息。 - 变量名错误:代码最后用
pd.DataFrame(all_jobs),但实际定义的变量是jobs_list,直接导致数据丢失报错。
修复方案
针对上述问题,调整后的完整代码如下:
import time import pandas as pd from selenium import webdriver from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC login_page_link = 'https://www.linkedin.com/login' search_page_link = 'https://www.linkedin.com/jobs/search/?geoId=101452733&keywords=data%20analyst&location=Australia&refresh=true' job_list_item_class = 'jobs-search-results__list-item' job_title_class = 'jobs-unified-top-card__job-title' company_name_class = 'jobs-unified-top-card__company-name' # 补充缺失的登录元素定位变量 username_id = 'username' password_id = 'password' login_btn_xpath = '//button[@type="submit"]' def get_browser_driver(): browser = webdriver.Chrome() browser.maximize_window() return browser def login_to_linkedin(browser): browser.get(login_page_link) # 显式等待登录元素加载完成 WebDriverWait(browser, 10).until( EC.presence_of_element_located((By.ID, username_id)) ).send_keys("username@mail.com") browser.find_element(by=By.ID, value=password_id).send_keys("pwd") login_btn = WebDriverWait(browser, 10).until( EC.element_to_be_clickable((By.XPATH, login_btn_xpath)) ) login_btn.click() # 等待登录跳转完成 WebDriverWait(browser, 15).until( EC.url_contains("feed") ) def load_search_results_page(browser): browser.get(search_page_link) # 等待搜索结果加载 WebDriverWait(browser, 15).until( EC.presence_of_element_located((By.CLASS_NAME, job_list_item_class)) ) # 滚动页面加载所有可见职位卡片 last_height = browser.execute_script("return document.body.scrollHeight") while True: browser.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(3) new_height = browser.execute_script("return document.body.scrollHeight") if new_height == last_height: break last_height = new_height def get_element_text_by_classname(browser, class_name): try: element = WebDriverWait(browser, 10).until( EC.visibility_of_element_located((By.CLASS_NAME, class_name)) ) return element.text except NoSuchElementException: return "" def get_job_post_data(browser): job_posts = [] # 获取总职位数,控制循环次数 total_jobs = len(browser.find_elements(by=By.CLASS_NAME, value=job_list_item_class)) for i in range(total_jobs): try: # 每次循环重新获取职位卡片,避免失效元素问题 job_cards = WebDriverWait(browser, 10).until( EC.presence_of_all_elements_located((By.CLASS_NAME, job_list_item_class)) ) job_card = job_cards[i] job_id = job_card.get_attribute('data-occludable-job-id') # 滚动到目标卡片并点击 browser.execute_script("arguments[0].scrollIntoView({block: 'center'});", job_card) WebDriverWait(browser, 10).until( EC.element_to_be_clickable(job_card) ).click() # 验证当前加载的详情Job ID是否匹配目标卡片 current_job_id = WebDriverWait(browser, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, f"div[data-job-id='{job_id}']")) ).get_attribute('data-job-id') if current_job_id != job_id: continue # 不匹配则跳过,避免重复数据 # 提取数据 job_dict = { 'Job ID': job_id, 'Job title': get_element_text_by_classname(browser, job_title_class), 'Company name': get_element_text_by_classname(browser, company_name_class) } job_posts.append(job_dict) except StaleElementReferenceException: # 遇到失效元素,重新执行当前循环 continue except Exception as e: print(f"处理职位时出错: {e}") continue return job_posts # 执行流程 browser = get_browser_driver() try: login_to_linkedin(browser) load_search_results_page(browser) jobs_list = get_job_post_data(browser) jobs_df = pd.DataFrame(jobs_list) print(jobs_df.head()) # 可选:保存到CSV文件 jobs_df.to_csv('linkedin_jobs.csv', index=False) finally: browser.quit()
关键修改说明
- 显式等待替代固定sleep:所有元素操作前用
WebDriverWait确保元素可交互/可见,提升稳定性。 - 循环内重新获取卡片:每次循环重新定位
job_cards,避免页面更新导致的失效元素错误。 - Job ID验证机制:点击卡片后验证详情区域的Job ID,确保提取的数据与目标职位匹配。
- 滚动加载所有卡片:进入详情页前先滚动页面加载所有可见职位,避免漏抓。
- 异常捕获处理:增加对
StaleElementReferenceException的捕获,遇到失效元素时重新尝试当前循环。 - 变量错误修复:补充缺失的登录元素定位变量,修正
all_jobs为jobs_list,并添加浏览器退出的finally块。
内容的提问来源于stack exchange,提问作者Fleur
相关产品推荐
相关产品推荐

