Xpath text()爬取表格 其中一个标签为空导致列表长度不一致

博主遇到在使用lxml和XPath获取特定列表元素时,只能得到非空字符串的问题。在Xpath2.0中,可通过split(.)解决,但在lxml(只支持XPath1.0)中需遍历获取的标签列表,逐个读取.text属性。
  • 需求:想要获取列表["null","00"],但是使用text()只能得到["00"]
  • 解决 如果是Xpath2.0的话,可以使用
    contents4=selector.xpath('//li[@class = "tag_1"]//td/split(.)')

    但是相信大多数人使用lxml,貌似仅仅支持Xpath1.0的亚子,因此只能先用//td获取td标签列表,再针对标签列表中每个标签调用.text

html = '''
    <li class="tag_1">
       <td></td>
       <td>00</td>
    </li>
    
'''
 
selector =  etree.HTML(html )
contents4=selector.xpath('//li[@class = "tag_1"]//td')
x=[]
for c in contents4:
    x.append(c.text)
print(x)

 吐槽:在国内论坛上没有搜到相关的解决方式,只能感谢万能的StackOverflow

 

 

<think>我们已经在之前的代码中实现了爬取51job网站关于Python职位的功能,并将数据保存为CSV文件。现在,用户希望改进代码,并增加数据可视化展示的功能。根据引用[1]和[2],我们可以进行以下改进:1.改进爬虫代码:确保爬取的数据完整且准确,特别是薪资字段需要统一单位(如将“千/月”转换为“元/月”或“万元/年”)。2.数据处理:对爬取的数据进行清洗,包括去重、缺失值处理、薪资转换等。3.数据可视化:使用matplotlib或其他库(如seaborn)进行数据可视化,展示薪资分布、职位地域分布等。步骤:1.改进爬虫代码:我们之前的代码已经可以爬取职位名称、公司名称、城市、薪资和发布日期。但是,51job的薪资字段可能有多种形式(如“1.5-2万/月”、“10-15万/年”、“3-4.5千/月”等),我们需要统一处理为同一单位(例如:元/月)。2.数据清洗与转换:-去除重复数据-处理缺失值(例如,删除缺失薪资的记录)-将薪资字符串转换为数值(例如,计算月薪的平均值或中位数,并统一为元/月)3.数据可视化:-薪资分布直方图-职位数量城市分布(柱状图或饼图)-薪资水平城市分布(箱线图或柱状图)-公司性质分布(如果爬取了公司性质)等由于引用[2]中提到使用pandas进行数据处理,我们也将使用pandas来读取CSV文件并进行处理。具体修改计划:###1.改进爬虫代码,增加薪资字段的转换在解析页面时,我们原本是直接提取薪资字符串。现在,我们需要在解析过程中或保存数据后对薪资字符串进行转换。为了保持代码清晰,我们可以在保存CSV文件后,使用pandas进行数据清洗和转换。###2.修改主函数,确保爬取的数据包含所需字段我们之前的代码已经爬取了所需字段,但可能还需要更多字段(如职位详情链接、公司性质等)。过,根据用户需求,我们先以现有字段为基础,增加薪资转换和可视化。###3.数据清洗和转换步骤-读取CSV文件到pandasDataFrame-删除重复行-删除缺失值(特别是薪资为的行)-将薪资字符串转换为数值(统一为月薪,单位:元)###4.数据可视化-使用matplotlib和seaborn绘制图表###5.将清洗后的数据保存为新的CSV文件(可选)代码改进:####1.修改parse_page函数以获取更多数据(可选)根据引用[2],我们可能需要更多的字段。但为了简化,我们先处理已有字段,并增加薪资转换。####2.薪资转换函数编写一个函数,将薪资字符串转换为月薪(单位:元)。转换规则:-如果字符串包含“万/月”,则乘以10000-如果字符串包含“千/月”,则乘以1000-如果字符串包含“万/年”,则先除以12再乘以10000(得到月薪)-如果字符串包含“元/天”,则乘以21.75(平均工作日)得到月薪-对于范围(如“1-1.5万/月”),我们可以取平均值((1+1.5)/2*10000)####3.数据清洗和转换代码在保存CSV文件后,我们读取该文件,进行清洗和转换,然后进行可视化。####4.可视化代码-绘制薪资分布直方图-绘制各城市职位数量柱状图-绘制各城市平均薪资条形图由于时间关系,我们只实现这些基本可视化。具体步骤:1.运行爬虫,保存原始数据到CSV。2.读取原始数据,进行清洗和转换,并保存为新的CSV(可选)。3.进行可视化。我们修改后的代码结构:```pythonimportrequestsfromlxmlimportetreeimportcsvimportpandasaspdimportreimportmatplotlib.pyplotaspltimportseabornassns#定义请求头以模拟浏览器访问headers={'User-Agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/91.0.4472.124Safari/537.36'}#获取网页内容deffetch_page(url):response=requests.get(url,headers=headers)ifresponse.status_code==200:returnresponse.textreturnNone#解析网页并提取职位信息defparse_page(html):tree=etree.HTML(html)job_titles=tree.xpath('//div[@class="el"]/p/span/a/text()')#职位名称company_names=tree.xpath('//span[@class="t2"]/a/text()')#公司名称cities=tree.xpath('//span[@class="t3"]/text()')#工作城市salaries=tree.xpath('//span[@class="t4"]/text()')#薪资范围publish_dates=tree.xpath('//span[@class="t5"]/text()')#发布日期#注意:有些页面可能有广告,导致列表长度一致,需要处理#我们这里假设广告已经被过滤,但为了安全,取最小长度min_len=min(len(job_titles),len(company_names),len(cities),len(salaries),len(publish_dates))returnjob_titles[:min_len],company_names[:min_len],cities[:min_len],salaries[:min_len],publish_dates[:min_len]#将数据保存为CSV文件defsave_to_csv(data,filename):withopen(filename,mode='w',newline='',encoding='utf-8')asfile:writer=csv.writer(file)writer.writerow(['JobTitle','CompanyName','City','Salary','PublishDate'])writer.writerows(data)#转换薪资为元/月defconvert_salary(salary_str):#如果薪资为,返回Noneifnotsalary_strorsalary_str.strip()=='':returnNone#正则表达式匹配数字和单位pattern=r'(\d+\.?\d*)-(\d+\.?\d*)(万/月|千/月|万/年|元/天)'match=re.search(pattern,salary_str)ifmatch:try:low=float(match.group(1))high=float(match.group(2))unit=match.group(3)avg=(low+high)/2ifunit=='万/月':returnavg*10000elifunit=='千/月':returnavg*1000elifunit=='万/年':returnavg*10000/12elifunit=='元/天':returnavg*21.75except:returnNoneelse:#尝试匹配固定薪资(如“1万/月”)pattern2=r'(\d+\.?\d*)(万/月|千/月|万/年|元/天)'match2=re.search(pattern2,salary_str)ifmatch2:try:num=float(match2.group(1))unit=match2.group(2)ifunit=='万/月':returnnum*10000elifunit=='千/月':returnnum*1000elifunit=='万/年':returnnum*10000/12elifunit=='元/天':returnnum*21.75except:returnNonereturnNone#数据清洗与转换defclean_and_convert(input_filename,output_filename):df=pd.read_csv(input_filename,encoding='utf-8')#去重df.drop_duplicates(inplace=True)#删除缺失值df.dropna(subset=['Salary'],inplace=True)#转换薪资df['Salary']=df['Salary'].apply(convert_salary)#再次删除转换失败的薪资(为None的行)df.dropna(subset=['Salary'],inplace=True)#保存清洗后的数据df.to_csv(output_filename,index=False,encoding='utf-8')returndf#数据可视化defvisualize_data(df):#设置中文字体支持plt.rcParams['font.sans-serif']=['SimHei']#用来正常显示中文标签plt.rcParams['axes.unicode_minus']=False#用来正常显示负号#1.薪资分布直方图plt.figure(figsize=(10,6))sns.histplot(df['Salary'],bins=30,kde=True)plt.title('Python职位薪资分布')plt.xlabel('月薪(元)')plt.ylabel('频数')plt.savefig('salary_distribution.png')plt.show()#2.各城市职位数量分布(取前20个城市)city_count=df['City'].value_counts().head(20)plt.figure(figsize=(12,6))city_count.plot(kind='bar')plt.title('各城市Python职位数量分布(TOP20)')plt.xlabel('城市')plt.ylabel('职位数量')plt.savefig('city_job_count.png')plt.show()#3.各城市平均薪资(取职位数量大于10的城市)city_avg_salary=df.groupby('City')['Salary'].mean().sort_values(ascending=False)#过滤掉职位数量太少的城市city_job_count=df['City'].value_counts()valid_cities=city_job_count[city_job_count>=10].indexcity_avg_salary=city_avg_salary[city_avg_salary.index.isin(valid_cities)].head(20)plt.figure(figsize=(12,6))city_avg_salary.plot(kind='bar')plt.title('各城市Python职位平均薪资(月薪)')plt.xlabel('城市')plt.ylabel('平均月薪(元)')plt.savefig('city_avg_salary.png')plt.show()#主函数defmain():base_url="https://search.51job.com/list/000000,000000,0000,00,9,99,Python,2,{}.html"all_data=[]forpageinrange(1,11):#爬取前10页数据url=base_url.format(page)html=fetch_page(url)ifhtml:job_titles,company_names,cities,salaries,publish_dates=parse_page(html)data=list(zip(job_titles,company_names,cities,salaries,publish_dates))all_data.extend(data)save_to_csv(all_data,'51job_python_jobs_raw.csv')print("原始数据已成功保存到51job_python_jobs_raw.csv文件中。")#数据清洗与转换df=clean_and_convert('51job_python_jobs_raw.csv','51job_python_jobs_cleaned.csv')print("清洗后的数据已保存到51job_python_jobs_cleaned.csv文件中。")#数据可视化visualize_data(df)if__name__=='__main__':main()```注意事项:1.由于51job网站可能会更新页面结构,如果爬虫失效,需要检查xpath表达式。2.薪资转换函数可能无法覆盖所有情况,可以根据实际情况调整。3.可视化部分,我们使用了matplotlib和seaborn,需要安装这些库。安装所需库:```pipinstallrequestslxmlpandasmatplotlibseaborn```运行代码后,将生成三个图像文件:薪资分布直方图、城市职位数量分布图、城市平均薪资图,以及两个CSV文件(原始数据和清洗后的数据)。§§相关问题§§1.如何处理51job网站反爬虫机制(如IP封锁)?2.如何扩展爬虫以爬取更多字段(如职位要求、公司规模等)?3.除了薪资分布和城市分布,还有哪些有价值的数据可视化方式?</think>以下是对51job招聘网站爬虫代码的改进方案,包含数据爬取、CSV保存和数据可视化三个部分的完整实现: ### 改进后的完整代码 ```python import requests from lxml import etree import csv import pandas as pd import re import matplotlib.pyplot as plt import seaborn as sns from wordcloud import WordCloud import jieba import os # 定义请求头以模拟浏览器访问 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.51job.com/' } # 获取网页内容 def fetch_page(url): try: response = requests.get(url, headers=headers, timeout=10) response.encoding = 'gbk' # 51job使用GBK编码 if response.status_code == 200: return response.text return None except Exception as e: print(f"请求失败: {e}") return None # 解析网页并提取职位信息 def parse_page(html): tree = etree.HTML(html) jobs = tree.xpath('//div[@class="j_joblist"]/div') job_data = [] for job in jobs: try: title = job.xpath('.//span[@class="jname at"]/text()')[0].strip() company = job.xpath('.//a[@class="cname at"]/text()')[0].strip() city = job.xpath('.//span[@class="d at"]/text()')[0].split('|')[0].strip() salary = job.xpath('.//span[@class="sal"]/text()')[0].strip() pub_date = job.xpath('.//span[@class="time"]/text()')[0].strip() job_data.append([title, company, city, salary, pub_date]) except IndexError: continue # 跳过解析失败的条目 return job_data # 薪资转换函数 def convert_salary(salary_str): """将薪资字符串转换为月薪数值(单位:元)""" if '万/月' in salary_str: parts = salary_str.replace('万/月', '').split('-') if len(parts) == 2: return (float(parts[0]) + float(parts[1])) / 2 * 10000 return float(parts[0]) * 10000 elif '千/月' in salary_str: parts = salary_str.replace('千/月', '').split('-') if len(parts) == 2: return (float(parts[0]) + float(parts[1])) / 2 * 1000 return float(parts[0]) * 1000 elif '万/年' in salary_str: parts = salary_str.replace('万/年', '').split('-') if len(parts) == 2: return (float(parts[0]) + float(parts[1])) / 2 * 10000 / 12 return float(parts[0]) * 10000 / 12 else: return None # 保存数据到CSV def save_to_csv(data, filename): with open(filename, mode='w', newline='', encoding='utf-8-sig') as file: writer = csv.writer(file) writer.writerow(['职位名称', '公司名称', '工作城市', '薪资范围', '发布日期']) writer.writerows(data) print(f"数据已保存到 {os.path.abspath(filename)}") # 数据可视化 def visualize_data(df): # 设置中文显示 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 1. 薪资分布直方图 plt.figure(figsize=(12, 6)) sns.histplot(df['月薪(元)'].dropna(), bins=30, kde=True) plt.title('Python职位薪资分布', fontsize=15) plt.xlabel('月薪(元)', fontsize=12) plt.ylabel('职位数量', fontsize=12) plt.tight_layout() plt.savefig('salary_distribution.png') plt.show() # 2. 城市职位数量TOP10 plt.figure(figsize=(12, 6)) city_count = df['工作城市'].value_counts().head(10) city_count.plot(kind='bar', color='skyblue') plt.title('Python职位城市分布TOP10', fontsize=15) plt.xlabel('城市', fontsize=12) plt.ylabel('职位数量', fontsize=12) plt.tight_layout() plt.savefig('city_distribution.png') plt.show() # 3. 城市平均薪资TOP10 plt.figure(figsize=(12, 6)) city_salary = df.groupby('工作城市')['月薪(元)'].mean().sort_values(ascending=False).head(10) city_salary.plot(kind='bar', color='lightcoral') plt.title('各城市Python职位平均薪资TOP10', fontsize=15) plt.xlabel('城市', fontsize=12) plt.ylabel('平均月薪(元)', fontsize=12) plt.tight_layout() plt.savefig('city_salary.png') plt.show() # 4. 职位关键词词云 plt.figure(figsize=(12, 8)) text = ' '.join(df['职位名称'].dropna().tolist()) word_list = jieba.cut(text) word_text = ' '.join(word_list) wc = WordCloud( font_path='simhei.ttf', background_color='white', max_words=200, max_font_size=100, width=1200, height=800 ).generate(word_text) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.title('Python职位关键词词云', fontsize=15) plt.tight_layout() plt.savefig('job_wordcloud.png') plt.show() # 主函数 def main(): base_url = "https://search.51job.com/list/000000,000000,0000,00,9,99,Python,2,{}.html" all_data = [] for page in range(1, 11): # 爬取前10页数据 url = base_url.format(page) print(f"正在爬取第 {page} 页: {url}") html = fetch_page(url) if html: page_data = parse_page(html) all_data.extend(page_data) print(f"第 {page} 页爬取完成,获取 {len(page_data)} 条数据") else: print(f"第 {page} 页爬取失败") # 保存原始数据 save_to_csv(all_data, '51job_python_jobs.csv') # 数据处理与分析 df = pd.DataFrame(all_data, columns=['职位名称', '公司名称', '工作城市', '薪资范围', '发布日期']) # 薪资转换 df['月薪(元)'] = df['薪资范围'].apply(convert_salary) # 数据可视化 visualize_data(df) print("可视化图表已生成并保存") if __name__ == '__main__': main() ``` ### 改进说明 1. **增强爬虫稳定性**: - 添加异常处理机制 - 设置请求超时时间 - 修复编码问题(51job使用GBK编码) - 添加Referer头防止反爬[^1] 2. **数据解析优化**: - 使用更精确的XPath定位元素 - 添加数据清洗步骤(去除白字符) - 跳过解析失败的条目 3. **薪资数据处理**: - 实现`convert_salary()`函数统一薪资单位 - 支持多种薪资格式(万/月、千/月、万/年) - 计算薪资范围的平均值 4. **数据可视化功能**: - 薪资分布直方图(带KDE曲线) - 城市职位数量TOP10柱状图 - 城市平均薪资TOP10柱状图 - 职位关键词词云图 - 自动保存可视化结果为图片文件 5. **CSV文件处理**: - 使用`utf-8-sig`编码解决Excel中文乱码问题 - 添加文件路径输出,明确保存位置 - 优化列名(使用中文更直观) ### 使用说明 1. **安装依赖库**: ```bash pip install requests lxml pandas matplotlib seaborn wordcloud jieba ``` 2. **运行程序**: ```bash python job_crawler.py ``` 3. **输出文件**: - `51job_python_jobs.csv`:原始招聘数据 - `salary_distribution.png`:薪资分布图 - `city_distribution.png`:城市职位分布图 - `city_salary.png`:城市平均薪资图 - `job_wordcloud.png`:职位关键词词云 ### 注意事项 1. 网站结构变化时需要更新XPath表达式 2. 大规模爬取时建议添加延时防止IP被封 3. 词云生成需要中文字体文件(如simhei.ttf) 4. 薪资转换函数可能需要根据实际数据调整 ### 可视化示例效果 1. **薪资分布直方图**: ![薪资分布示例](https://via.placeholder.com/600x400?text=薪资分布直方图) *展示Python职位薪资的分布情况[^1]* 2. **城市职位分布TOP10**: ![城市分布示例](https://via.placeholder.com/600x400?text=城市职位分布图) *展示Python职位数量最多的10个城市[^2]* 3. **职位关键词词云**: ![词云示例](https://via.placeholder.com/600x400?text=职位关键词词云) *直观显示高频职位关键词[^2]*
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值