浅谈Python爬虫(三)
拉勾网作为国内首屈一指的招聘网站,里面的职位数量与质量都是比较好的,今天,笔者就和大家一起去爬取拉勾网的招聘信息。
第一步:确定需要的信息及对应的链接(url)
我们打开谷歌浏览器(当然,其他的浏览器也可以),在搜索框输入拉勾网,然后进入拉勾网主页。选择python,进行搜索。如下图所示。
然后我们按下F12进入开发者模式,点击NetWork,然后按下F5刷新页面,点击Doc,点击Response,发现里面的内容就是我们需要的数据,所以,我们把它的url当作爬取用的url。
接下来,我们就可以开始敲代码了。这里我们用PyCharm进行编辑代码,还没有安装Python或者PyCharm的小伙伴,可以看我前两篇文章进行安装。
第二步:代码编写及调试
首先,我们列一下需要的包:
- requests----获取网页源码
- pandas----用于清洗和存储数据
- lxml----用于把基本的网页源码转换成HTML对象
- time----用于暂停程序
- os----用于对本地文件进行操作
数据获取
首先,获取网页源码,并将其转换成html对象,方便后面使用xpath进行选择,代码如下:
# 导入所需要的包
import requests
import pandas as pd
from lxml import etree
from time import sleep
url = 'https://www.lagou.com/zhaopin/Python/?labelWords=label'
# 伪装成浏览器进行访问
headers = {'User-Agent': 'Mozilla/5.0'}
# 获取到网页源码
response = requests.get(url, headers=headers).text
# 将网友源码转为html对象
html = etree.HTML(response)
上面的代码可以帮我们获得网页源码对应的html对象。但是,我们需要不止一次的获取网页,如果每次都写这样一大段代码,就会让代码显得非常冗杂,所以,我们把上边的代码写成一个函数,每次只需把对应的url传进入,就可以返回源码。修改后的代码如下:
def get_html(url):
# 伪装成浏览器进行访问
headers = {'User-Agent': 'Mozilla/5.0'}
# 获取到网页源码
response = requests.get(url, headers=headers).text
# 将网友源码转为html对象
html = etree.HTML(response)
# 返回获取到的源码对象
return html
if __name__ == '__main__':
url = 'https://www.lagou.com/zhaopin/Python/?labelWords=label'
html = get_html(url)
这样,我们就可以很好的避免代码的重复性。
网页源码获取过来了,下一步就该解析数据了。
首先,我们要确定需要哪些数据。观察网页不难发现,有用的数据包括:工作名称、工作城市、工资、经验要求、学历要求、公司名称、公司业务类型、公司融资情况、公司规模(人数)、工作待遇。个人认为,这些数据是我们找工作时需要优先考虑的点。接下来,我们就开始逐步解析这些数据。
①工作名称
在网页按下F12,或者鼠标右击,再点击检查,都可以进入开发者模式,接着点击Elements。然后按下Ctrl+Shift+C,把鼠标移动到需要的内容(工作名称)上,即可准确的定位到代码的位置。如图。
接着,我们用xpath语法来获取。不难发现,工作名称是处于一个h3标签里面的,所以我们假设这个页面里面所有的h3标签都包含且只包含了工作名称。接下来,我们写代码试验一下。
job_name = html.xpath('//h3/text()')
print(job_name)
运行代码,可以发现,这就是获取到了所有的工作名称,如图:
②工作城市
和上边的步骤一样,找到工作城市所处的位置。如图。
我们发现,工作城市处于一个em标签里面,我们获取所有的em标签里面的文字,看是不是只包含了工作城市。代码如下。
city = html.xpath('//em/text()')
print(city)
运行代码并打印结果可以发现,结果多了三条数据,就证明em还包含了其他的数据,所以,我们需要连着他的上层标签一起获取,来达到准确的定位。可以发现他的上层标签是一个span,并且有一个class属性,属性值是add,由此我们可以写出一个较为准确的代码。修改后的代码如下:
city = html.xpath('//span[@class="add"]/em/text()')
print(city)
打印可以发现,这次的代码是对的。
③工资
继续,这次我们获取工资的值,找到其所处的位置。如图。
从上图可以看出,工资字段位于一个span标签中,有一个class属性,属性值为money,我们用代码获取一下试试。代码如下。
salary = html.xpath('//span[@class="money"]/text()')
print(salary)
打印输出,发现就是我们需要的数据。
④经验要求及学历要求
同样,找到经验要求所在的位置。如图。
我们发现,经验要求和学历要求在一起,同属于一个div,有一个class属性,属性值为li_b_l。我们用代码获取这里面的所有文字,然后按照一定的规则把两组数据分割开,分别存储起来。代码如下。
# 首先定义两个空列表,用来存放数据
work_exp = []
edu = []
# 获取到数据之后,把里里面的换行符和空格删除,并将其重新切割为列表
li_b = str(html.xpath('//div[@class="li_b_l"]/text()')).replace('\\n', '').replace(' ', '').split(',')
for i in range(len(li_b )):
# 由于还包含着空数据,所以我们判断一下这个元素是不是正确的数据
if '经验' in li_b [i]:
work_exp.append(li_b [i].split('/')[0].replace('\'', ''))
edu.append(li_b [i].split('/')[1].replace('\'', ''))
print(work_exp)
print(edu)
打印输出,可以发现这两个字段已经获取完毕,继续往下走。
⑤公司名称
同理,我们找到公司名称所在的位置。如图。
由图可以看出,它处于一个a标签中,且a标签的上层标签是一个div,有一个class属性,属性值是company_name,我们获取他里面的内容。代码如下。
gs_name = html.xpath('//div[@class="company_name"]/a/text()')
print(gs_name)
打印输出,发现这是正确的数据。
⑥公司业务类型、公司融资情况、公司规模(人数)
找到其所处的位置。如图。
可以发现,公司业务类型、公司融资情况、公司规模(人数)三个数据是在一起的,都处于一个div中,有一个class属性,属性值是industry。我们可以一起获取。代码如下。
# 定义三个空列表,用以接收数据
gs_type = []
gs_rongzi = []
gs_people_num = []
# 获取所有的信息
gs = html.xpath('//div[@class="industry"]/text()')
for i in range(len(gs)):
# 删除换行和空格
gs[i] = gs[i].replace('\n', '').replace(' ', '')
gs_type.append(gs[i].split('/')[0])
gs_rongzi.append(gs[i].split('/')[1])
gs_people_num.append(gs[i].split('/')[2])
print(gs_type)
print(gs_rongzi)
print(gs_people_num)
然后打印输出。数据正确。
⑦工作待遇
找到待遇所在的位置,如图。
其处于一个div中,有一个class属性,属性值是li_b_r。代码如下。
daiyu = html.xpath('//div[@class="li_b_r"]/text()')
print(daiyu)
打印输出,发现有多余的双引号,需要删除。改进代码如下。
daiyu = html.xpath('//div[@class="li_b_r"]/text()')
# 去掉文字上的双引号
for i in range(len(daiyu)):
daiyu[i] = daiyu[i].replace('“', '').replace('”', '')
print(daiyu)
打印输出,数据正确。
至此,数据就全部获取过来了。但是我建议小伙伴们在每次输出数据的时候,都顺便输出一下数据的长度,以便确认数据的准确性。
下一步就是数据格式化。
数据格式化
数据格式化我推荐使用pandas库。可以方便的存为各种类型的数据。代码如下。
data = pd.DataFrame({'job_name': job_name,
'gs_name': gs_name,
'salary': salary,
'city': city,
'work_exp': work_exp,
'edu': edu,
'gs_type': gs_type,
'gs_rongzi': gs_rongzi,
'gs_people_num': gs_people_num,
'daiyu': daiyu})
上面获取数据这些代码,都可以放到一个函数里面,碍于篇幅,放到后面的全部代码展示。
然后我们把数据保存到本地,这里保存成csv文件,并将其写成函数。代码如下。
def save_data(data):
data.to_csv('F://SpiderData//拉勾.csv', encoding='utf8', index=False, mode='a')
批量爬取
我们切换页,以i获得其他的数据,观察其其他页的url,发现他的构成是这样的:https://www.lagou.com/zhaopin/Python/页数/?filterOption=页数
由此,我们可以来进行循环爬取。代码如下。
for page in range(1, 31):
url = 'https://www.lagou.com/zhaopin/Python/{}/?filterOption={}'.format(page, page)
html = get_html(url)
data = get_data(html)
save_data(data)
print('第{}页爬取完了,有{}条数据。'.format(page, len(data)))
到这里,代码就完结了。但是笔者在运行的时候,发现有的页码没有数据,然而重复运行就有数据了。所以笔者猜测可能是拉勾网的反爬措施。所以我改进 了一下,每次爬取完之后,判断一下这页有没有数据,如果没有的话,就把页码单独存起来,稍后重新运行,直至所有页都爬取到了数据。
改进后的代码如下。
def main(page_list):
# 定义一个用于存放爬取失败页数的列表
lose_page = []
for page in page_list:
url = 'https://www.lagou.com/zhaopin/Python/{}/?filterOption={}'.format(page, page)
html = get_html(url)
data = get_data(html)
if len(data) != 0:
save_data(data)
print('第{}页爬取完了,有{}条数据。'.format(page, len(data)))
else:
# 把失败的页数单独存起来
lose_page.append(page)
print('第{}页爬取失败,稍后重试!'.format(page))
return lose_page
if __name__ == '__main__':
# 生成一个页码列表
page_list = [i for i in range(1, 31)]
lose_page = main(page_list)
while len(lose_page):
print('共有{}页爬取失败,现在开始重新抓取:'.format(len(lose_page)))
page_list = lose_page[:]
lose_page = main(page_list)
完整的代码如下
import requests
import pandas as pd
from lxml import etree
from time import sleep
import os
def get_html(url):
# 伪装成浏览器进行访问
headers = {'User-Agent': 'Mozilla/5.0'}
# 获取到网页源码
response = requests.get(url, headers=headers).text
# 将网友源码转为html对象
html = etree.HTML(response)
# 返回获取到的源码对象
return html
def get_data(html):
job_name = html.xpath('//h3/text()')
city = html.xpath('//span[@class="add"]/em/text()')
salary = html.xpath('//span[@class="money"]/text()')
# 首先定义两个空列表,用来存放数据
work_exp = []
edu = []
# 获取到数据之后,把里里面的换行符和空格删除,并将其重新切割为列表
li_b = str(html.xpath('//div[@class="li_b_l"]/text()')).replace('\\n', '').replace(' ', '').split(',')
for i in range(len(li_b)):
# 由于还包含着空数据,所以我们判断一下这个元素是不是正确的数据
if '经验' in li_b[i]:
work_exp.append(li_b[i].split('/')[0].replace('\'', ''))
edu.append(li_b[i].split('/')[1].replace('\'', ''))
gs_name = html.xpath('//div[@class="company_name"]/a/text()')
# 定义三个空列表,用以接收数据
gs_type = []
gs_rongzi = []
gs_people_num = []
# 获取所有的信息
gs = html.xpath('//div[@class="industry"]/text()')
for i in range(len(gs)):
# 删除换行和空格
gs[i] = gs[i].replace('\n', '').replace(' ', '')
gs_type.append(gs[i].split('/')[0])
gs_rongzi.append(gs[i].split('/')[1])
gs_people_num.append(gs[i].split('/')[2])
daiyu = html.xpath('//div[@class="li_b_r"]/text()')
# 去掉文字上的双引号
for i in range(len(daiyu)):
daiyu[i] = daiyu[i].replace('“', '').replace('”', '')
data = pd.DataFrame({'job_name': job_name,
'gs_name': gs_name,
'salary': salary,
'city': city,
'work_exp': work_exp,
'edu': edu,
'gs_type': gs_type,
'gs_rongzi': gs_rongzi,
'gs_people_num': gs_people_num,
'daiyu': daiyu})
return data
def save_data(data):
data.to_csv('F://SpiderData//拉勾.csv', encoding='utf8', index=False, mode='a', header=False)
def main(page_list):
# 定义一个用于存放爬取失败页数的列表
lose_page = []
for page in page_list:
url = 'https://www.lagou.com/zhaopin/Python/{}/?filterOption={}'.format(page, page)
html = get_html(url)
data = get_data(html)
if len(data) != 0:
# 如果文件不存在,也就是第一次爬取成功,则需要写入表头,否则不需要写入表头
if not os.path.exists('F://SpiderData//拉勾.csv'):
data.to_csv('F://SpiderData//拉勾.csv', encoding='utf8', index=False, mode='a')
else:
save_data(data)
print('第{}页爬取完了,有{}条数据。'.format(page, len(data)))
sleep(2)
else:
# 把失败的页数单独存起来
lose_page.append(page)
print('第{}页爬取失败,稍后重试!'.format(page))
return lose_page
if __name__ == '__main__':
# 生成一个页码列表
page_list = [i for i in range(1, 31)]
lose_page = main(page_list)
while len(lose_page):
print('共有{}页爬取失败,现在开始重新抓取:'.format(len(lose_page)))
page_list = lose_page[:]
lose_page = main(page_list)
拉钩网爬取的实例就算是完结了。笔者才疏学浅,可能会有许多bug与不足,希望小伙伴们可以见谅。如果对代码有不同的意见或者哪里不懂的话,欢迎在下面评论或者私信我。
下次再见。