Python高级应用程序设计任务要求
用Python实现一个面向主题的网络爬虫程序,并完成以下内容:
(注:每人一题,主题内容自选,所有设计内容与源代码需提交到博客园平台)
一、主题式网络爬虫设计方案(15分)
1.主题式网络爬虫名称
基于requests库抓取实习僧网站进行岗位分析
2.爬虫架构设计
受爬虫中反爬策略影响,为了能够顺利抓取不影响本机ip的情况下选择使用代理的方式进行数据的抓取。于是我将爬虫的抓取部分分为4个部分,通过这4个部分来进行数据的抓取。
3.主题式网络爬虫爬取的内容与数据特征分析
抓取不同城市的岗位要求信息进行分析。
我们目的是对北上广深杭5个城市进行岗位需求统计,来反应近些年内哪个城市发展最为迅速(发展快对于各个岗位的需求量都会增大)。
4主题式网络爬虫设计方案概述(包括实现思路与技术难点)
实现思路:
(1)利用requests方法请求网页。
(2)利用BeautifuSoup进行网页解析,同时利用其语法获取需要的数据信息。
(3)利用循环实现翻页获取数据。
技术难点:
(1)网站的反爬策略
(2)代理访问本身的IP地址的不稳定性
(3)对数据的解析
遇到难题:
一开始我们是以Boss直聘网站进行的抓取的,但是后来发现Boss直聘网站在今年10月份更新了反爬策略。我们在抓取二级页面时,一直都是在一个等待界面。我百度了一下,又说是因为重定向的问题
会导致这样。我查阅了一些论坛,说重定向有三种,一种是在服务器中做重定向。这种方式的重定向是会返回301或302的状态码以及一个跳转连接,但是我们的返回代码是200。因此不是这个原因导致的。
第二种meta refresh,即网页中的标签声明了网页重定向的链接,这种重定向由浏览器完成。但是我在meta标签下并没有看到local这个属性,通常会有一个url在标签当中,但是我并没有找到。第三种
是js重定向,但是我们并没有学习过js,于是我多方求助,让别人帮我看了几千行的js代码,终于发现了问题所在。在js代码当中有一个z_token字段,该字段是一个随机生成的字符串,相当长。将该字段放入
cookie中便可以访问该页面。我没有找到方式获取这个字段,同时该字段有效时长仅为2分钟这就断绝了像使用headers的方法来访问。我咨询了学习前后端开发的同学,了解到因为这个字段为所及生成的,即
使使用同样的函数去生成因为不是同一次调用返回的结果也不会相同。这个问题卡住了我们很久。后续了解到selenium库可以模拟浏览器进行访问,这样就可以让浏览器自动的获取到那个字段进行访问。这样
带来的问题是抓取速度得进行限制同时我也不清楚其使用代理的方式。在查找相关资料的过程中,我发现了通过Selenium库进行模拟是,有一个字段是会提示自动化脚本的,一旦网站截取到了这个字段就依然
会被屏蔽 ,且不能通过简单覆盖消除。因为时间的问题,并不允许后续问题出现的太多,所以我们后面更换了爬取网站。不确定因素太多容易导致超出我们的预期。
在换目标网站前,我们完成了抓取方案的设计,对网页的二级页面分析完毕。由于二级页面访问问题卡了3周左右,导致项目进程缓慢,后续工作无法展开。
二、主题页面的结构特征分析(15分)
1.主题页面的结构特征
二级页面:
二级页面中的目标岗位详细信息都放在这样的一个a标签中,我们可以通过BeautifulSoup类的find_all方法获取到所有的这个一个a标签。对这样的一个返回参数进行迭代,迭代时对象是tag,通过使用tag[‘key’]的方式获取到其href中的三级页面标签。
三级页面:
三级页面中有多个div标签,岗位信息都在其中。我们通过BeautifulSoup方法获取每一块的标签内容,然后强转类型,通过get_text方法获取其中的字符。
这种方法就无需关注div内部的标签是否一直,获取对应的字符就可了。
三、网络爬虫程序设计(60分)
1.编程环境
我负责部分用到的是python 3.7 、pycharm、jupyter
2、同学负责部分抓取到的网页(爬取网页完整代码在后面呈现)
3、以下是我负责的部分
数据的解析
from bs4 importBeautifulSoupimportrequestsimportcsvimportpandas as pdimportbs4importxlwtimportos#def decrypt_text(text): # 数字解密处理函数#for key, value in mapping.items():#text = text.replace(key, value)#return text
job =[]
job_position=[]
money=[]
education=[]
time=[]
sx_time=[]
com_position=[]
job_detail=[]
company_name=[]
company_desc=[]
company_tags=[]
company_detail=[]defchuli(path):#下面是对页面进行解析的代码
#html = open(path, 'r', encoding='utf-8')
#html = decrypt_text(html) # 对html页面进去解密
soup = BeautifulSoup(open(path, 'r', encoding='utf-8'), 'html.parser')
job= soup.find(class_='new_job_name').get_text() #获取职位名称
job_detail = soup.find(class_='job_detail').get_text().replace('\n', '') #获取职位描述 # \n去掉换行,
com_position = soup.find(class_='com_position').get_text() #获取工作地点
job_position = soup.find(class_='job_position').get_text() #城市
company_name = soup.find(class_='com-name').get_text() #获取公司名称
company_desc = soup.find(class_='com-desc').get_text() #获取公司介绍
company_tags = soup.find(class_='com-tags').get_text().replace('\n', ' ') #获取公司福利
company_detail = soup.find(class_='com-detail').get_text().replace('\n', ' ') #获取公司性质 # \n用空格代替换行
money = soup.find(class_='job_money cutom_font').get_text() #获取薪资
education = soup.find(class_='job_academic').get_text() #获取学历要求
time = soup.find(class_='job_week cutom_font').get_text() #获取工作时长
sx_time = soup.find(class_='job_time cutom_font').get_text() #获取实习时间
shuju =[job, job_position, money, education, time, sx_time, com_position, job_detail, company_name, company_desc,
company_tags, company_detail]return(shuju)defwrite_text(i, shuju, sheet):for j inrange(len(shuju)):
sheet.write(i, j, shuju[j])#将数据写入excel表
defmain():
shuju=[]
k= 1 #控制第几个2级页面
n = 1 #控制第几个3级页面
p = 1 #控制excel中的当前行
#以下是将数据存到excel中
workbook = xlwt.Workbook() #定义workbook
sheet = workbook.add_sheet('数据分析', cell_overwrite_ok=True) #添加sheet
#写表头
head = ['职位名称', '城市', '薪资', '学历要求', '工作时长', '实习时间', '工作地点', '职位描述','公司名称', '公司介绍', '公司福利', '公司性质'] #表头
write_text(0, head, sheet)while (1):
n= 1
while (1):
path= 'C:/Users/fqh/PycharmProjects/pachong/html3/第{}页-{}.html'.format(k, n) #依次读取html页面
try:
open(path,'r', encoding='utf-8') #差错处理,当文件不存在时break
exceptFileNotFoundError:break
else:#正常的输入数据
write_text(p, chuli(path), sheet)
workbook.save('数据分析.xls')
p+= 1n+= 1
if (n == 20):breakk+= 1
if (k > 250):break
if __name__ == '__main__':#数据解密映射
#mapping = {'': '0', '': '1', '': '2', '': '3', '': '4',
#'': '5', '': '6', '': '7', '': '8', '': '9'}
main()
结果
问题:因为实习僧网站有反扒策略,数据被加密,所有在文件中数据没有被显示
数据清洗
#导入excel表
importpandas as pd
text=pd.DataFrame(pd.read_excel('数据分析.xls'))
text.head()
#查看重复值
text.duplicated()
#删除重复值
text=text.drop_duplicates()
#查看表的统计信息
text.describe()
#查看每个城市的岗位数量
text['城市'].value_counts()
数据可视化
#建立北上广深一线城市岗位需求的柱状图
df=pd.DataFrame([['北京','上海','广州','深圳','杭州'],[771,690,335,126,136]])
dfimportmatplotlib.pyplot as pltimportnumpy as npimportseaborn as snsfrom pylab importmpl
mpl.rcParams['font.sans-serif'] = ['SimHei']
plt.figure()
plt.bar(['北京','上海','广州','深圳','杭州'],[771,690,335,126,136])
plt.title('北上广深一线城市岗位需求量')
plt.savefig('北上广深一线城市岗位需求量图.jpg')
plt.show()
#建立软件行业在各地区的分布饼状图
city=text['城市'][text['职位名称'].str.contains('软件')].value_counts()
label=city.keys()
city_list=[]
count=0
n= 1distance=[]for i incity:
city_list.append(i)#print('列表长度', len(city_list))
count += 1
if count > 5:
n+= 0.1distance.append(n)else:
distance.append(0)
plt.pie(city_list, labels=label, labeldistance=1.2, autopct='%2.1f%%', pctdistance=0.6, shadow=True, explode=distance)
plt.axis('equal') #使饼图为正圆形
plt.legend(loc='upper left', bbox_to_anchor=(-0.1, 1))
plt.title('软件行业在各地区的分布')
plt.savefig('软件行业在各地区的分布图.jpg')
plt.show()
数据持久化
4、完整代码呈现
爬取部分(分为三个文件)
主函数
from Boss import *
from Message importMessageimportrequestsif __name__ == "__main__":
Boss1=Boss()#message=Message('html3')
#headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362'}
#解析数据时停用
Boss1.run(250)#message.run()
IPPool
importrequestsimportjsonfrom requests.exceptions import * #导入requests的异常库
classIPPool:
i= 0 #表示当前选中地址池中的哪一个地址
def __init__(self,url,max):
self.url=url #地址池要访问的地址
self.max=max #地址池的最大值
self.ipurl="http://webapi./getip?num={}&type=2&pro=&city=0&yys=0&port=11&time=1&ts=0&ys=0&cs=0&lb=1&sb=0&pb=4&mr=2®ions=".format(max)#获取json格式的地址资源
defset_IP(self):
r=requests.get(self.ipurl)
data=r.json() #对放回的json包进行解析
self.ip=data['data']
self.max=5
print(self.ip)defget_IP(self):
Error_flag=0
time= 10
if(len(self.ip)==0):
self.set_IP()#对超出范围的值归零,使地址池变成一个循环的表
if(self.i>=self.max-1):
self.i=0#若当前地址响应时间小于1s则使用该地址否则删除该地址
while(time>9):#若地址池为空,则重新申请一次地址
if(len(self.ip)==0):
self.set_IP()
self.i=0continue
if(self.i>=self.max):
self.set_IP()
self.i=0continuedata=self.ip[self.i]print(data['ip']+':'+str(data['port']))
Ip=str('https://'+data['ip']+':'+str(data['port']))if(Ip==""):
self.set_IP()
self.i=0continueproxies= {'https':Ip}print(proxies)#利用该地址尝试访问url
try:r=requests.get(self.url,proxies=proxies)except (ProxyError,SSLError):#对遇到的代理异常做处理
delself.ip[self.i]
self.max-=1self.i+=1
if(self.i>=self.max-1):
self.i=0while(1):#直到没有异常
try:r=requests.get(self.url,proxies=proxies)except(ProxyError,SSLError):try:delself.ip[self.i]exceptIndexError:
Error_flag=1self.set_IP()break;else:
self.max-=1self.i+=1
if(self.i>=self.max-1):
self.i=0else :break
else:if(self.i>=self.max-1):
self.i=0#得到响应的时间,单位s
time=r.elapsed.total_seconds()#超时的删除
if(time>9):if(self.i<=self.max):delself.ip[self.i]
self.max-=1self.i+=1
#if(self.n>15):#防止地址失效最快的解决方案
#self.set_IP()
#Error_flag==1
#self.n=0
#self.n+=1
if Error_flag==1:
Error_flag=0continue
return proxies
Boos
1 importre2 importrequests3 importjson4 from selenium importwebdriver5 from IPPool import *
6 from City import *
7 from bs4 importBeautifulSoup8 from requests.exceptions import *
9
10 #针对实习僧的爬取类
11 classBoss:12 num=013 #设置user-agent
14 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362',}15 url="/"
16 #初始化,设置url及筛选条件
17 def __init__(self):18 #对地址池进行初始化
19 self.ippool=IPPool(self.url,5)20self.ippool.set_IP()21 #设置登陆账号
22 deflogin(admin,passwd):23 self.admin=admin24 self.passwd=passwd25 #请求页面方法,返回一个response
26 defGet_1(self,href):27 Error_flag=1
28 while(Error_flag):29 Error_flag=030 proxies =self.ippool.get_IP()31 try:response = requests.get(href,headers=self.headers,proxies=proxies)32 except(ProxyError,SSLError):33 Error_flag=1
34 else:35 #response = brower.get(self.url)
36 print(response.status_code)37 returnresponse.content.decode()38 #保存html字串
39 defsave_html(self,html_str,file_path):40 with open(file_path,"w",encoding="utf-8")as f:41f.write(html_str)42 self.num+=1
43
44 #对标签进行截取函数
45 defget_tap(self,tag,key,name,soup1):46 for j in soup1.find_all(tag,class_=name):47 if(j[key]==name):48 message =BeautifulSoup(str(j))49 returnmessage50 #运行爬虫
51 defrun(self,MAX):52 k=250
53 n=1
54 message={}55 buf=''
56 response = self.Get_1("/interns?page=1&keyword=&type=intern&area=&months=&days=°ree=%E6%9C%AC%E7%A7%91&official=&enterprise=&salary=-0&publishTime=&sortType=&city=%E5%85%A8%E5%9B%BD&internExtend=")#获取到了页面的HTML代码
57 if(k>MAX):58 return059 file_path2 = 'data\{}.html'.format(k)#存放二级页面
60 with open(file_path2,"w",encoding = 'utf-8') as f:61f.write(response)62 soup = BeautifulSoup(open(file_path2,'r',encoding='utf-8'))63 print(soup.get_text)64 message1=BeautifulSoup(str(soup.find_all("div",id="__layout")))65 message2=BeautifulSoup(str(message1.find_all("div",class_="interns")))66 message3=BeautifulSoup(str(message2.find_all("div",class_="result-list clearfix")))67 message4=BeautifulSoup(str(message3.find_all("div",class_="primary-content f-l")))68 message5=BeautifulSoup(str(message4.find_all("div",style="display:;")))69 message=message5.find_all("a",class_="title ellipsis font")70 for j inmessage:71 print(j['href'])72 file_path3 = 'html3\第{}页-{}.html'.format(k,n)73 buf=j['href']74 r=self.Get_1(buf)75self.save_html(r,file_path3)76 n+=1
77 while(k
79 n=1
80 message={}81 buf=''
82 tag_url="/interns?page={}&keyword=&type=intern&area=&months=&days=°ree=%E6%9C%AC%E7%A7%91&official=&enterprise=&salary=-0&publishTime=&sortType=&city=%E5%85%A8%E5%9B%BD&internExtend=".format(k)83 response = self.Get_1(tag_url)#获取到了页面的HTML代码
84 if(k>=MAX):85 return086 file_path2 = 'data\{}.html'.format(k)#存放二级页面
87 with open(file_path2,"w",encoding = 'utf-8') as f:88f.write(response)89 soup = BeautifulSoup(open(file_path2,'r',encoding='utf-8'))90 message1=BeautifulSoup(str(soup.find_all("div",id="__layout")))91 message2=BeautifulSoup(str(message1.find_all("div",class_="interns")))92 message3=BeautifulSoup(str(message2.find_all("div",class_="result-list clearfix")))93 message4=BeautifulSoup(str(message3.find_all("div",class_="primary-content f-l")))94 message5=BeautifulSoup(str(message4.find_all("div",style="display:;")))95 message=message5.find_all("a",class_="title ellipsis font")96 for j inmessage:97 file_path3 = 'html3\第{}页-{}.html'.format(k,n)98 buf=j['href']99 r=self.Get_1(buf)100self.save_html(r,file_path3)101 n+=1
数据解析
from bs4 importBeautifulSoupimportrequestsimportcsvimportpandas as pdimportbs4importxlwtimportos#def decrypt_text(text): # 数字解密处理函数#for key, value in mapping.items():#text = text.replace(key, value)#return text
job =[]
job_position=[]
money=[]
education=[]
time=[]
sx_time=[]
com_position=[]
job_detail=[]
company_name=[]
company_desc=[]
company_tags=[]
company_detail=[]defchuli(path):#下面是对页面进行解析的代码
#html = open(path, 'r', encoding='utf-8')
#html = decrypt_text(html) # 对html页面进去解密
soup = BeautifulSoup(open(path, 'r', encoding='utf-8'), 'html.parser')
job= soup.find(class_='new_job_name').get_text() #获取职位名称
job_detail = soup.find(class_='job_detail').get_text().replace('\n', '') #获取职位描述 # \n去掉换行,
com_position = soup.find(class_='com_position').get_text() #获取工作地点
job_position = soup.find(class_='job_position').get_text() #城市
company_name = soup.find(class_='com-name').get_text() #获取公司名称
company_desc = soup.find(class_='com-desc').get_text() #获取公司介绍
company_tags = soup.find(class_='com-tags').get_text().replace('\n', ' ') #获取公司福利
company_detail = soup.find(class_='com-detail').get_text().replace('\n', ' ') #获取公司性质 # \n用空格代替换行
money = soup.find(class_='job_money cutom_font').get_text() #获取薪资
education = soup.find(class_='job_academic').get_text() #获取学历要求
time = soup.find(class_='job_week cutom_font').get_text() #获取工作时长
sx_time = soup.find(class_='job_time cutom_font').get_text() #获取实习时间
shuju =[job, job_position, money, education, time, sx_time, com_position, job_detail, company_name, company_desc,
company_tags, company_detail]return(shuju)defwrite_text(i, shuju, sheet):for j inrange(len(shuju)):
sheet.write(i, j, shuju[j])#将数据写入excel表
defmain():
shuju=[]
k= 1 #控制第几个2级页面
n = 1 #控制第几个3级页面
p = 1 #控制excel中的当前行
#以下是将数据存到excel中
workbook = xlwt.Workbook() #定义workbook
sheet = workbook.add_sheet('数据分析', cell_overwrite_ok=True) #添加sheet
#写表头
head = ['职位名称', '城市', '薪资', '学历要求', '工作时长', '实习时间', '工作地点', '职位描述','公司名称', '公司介绍', '公司福利', '公司性质'] #表头
write_text(0, head, sheet)while (1):
n= 1
while (1):
path= 'C:/Users/fqh/PycharmProjects/pachong/html3/第{}页-{}.html'.format(k, n) #依次读取html页面
try:
open(path,'r', encoding='utf-8') #差错处理,当文件不存在时break
exceptFileNotFoundError:break
else:#正常的输入数据
write_text(p, chuli(path), sheet)
workbook.save('数据分析.xls')
p+= 1n+= 1
if (n == 20):breakk+= 1
if (k > 250):break
if __name__ == '__main__':#数据解密映射
#mapping = {'': '0', '': '1', '': '2', '': '3', '': '4',
#'': '5', '': '6', '': '7', '': '8', '': '9'}
main()
数据清洗和可视化
#数据清洗#导入excel表
importpandas as pd
text=pd.DataFrame(pd.read_excel('数据分析.xls'))
text.head()#查看重复值
text.duplicated()#删除重复值
text=text.drop_duplicates()#查看表的统计信息
text.describe()#查看每个城市的岗位数量
text['城市'].value_counts()#数据可视化#建立北上广深一线城市岗位需求的柱状图
df=pd.DataFrame([['北京','上海','广州','深圳','杭州'],[771,690,335,126,136]])
dfimportmatplotlib.pyplot as pltimportnumpy as npimportseaborn as snsfrom pylab importmpl
mpl.rcParams['font.sans-serif'] = ['SimHei']
plt.figure()
plt.bar(['北京','上海','广州','深圳','杭州'],[771,690,335,126,136])
plt.title('北上广深一线城市岗位需求量')
plt.savefig('北上广深一线城市岗位需求量图.jpg')
plt.show()#建立软件行业在各地区的分布饼状图
city=text['城市'][text['职位名称'].str.contains('软件')].value_counts()
label=city.keys()
city_list=[]
count=0
n= 1distance=[]for i incity:
city_list.append(i)#print('列表长度', len(city_list))
count += 1
if count > 5:
n+= 0.1distance.append(n)else:
distance.append(0)
plt.pie(city_list, labels=label, labeldistance=1.2, autopct='%2.1f%%', pctdistance=0.6, shadow=True, explode=distance)
plt.axis('equal') #使饼图为正圆形
plt.legend(loc='upper left', bbox_to_anchor=(-0.1, 1))
plt.title('软件行业在各地区的分布')
plt.savefig('软件行业在各地区的分布图.jpg')
plt.show()
四.结论
通过观看第一个柱状图了解到,目前北上广的岗位需求依旧十分巨大。对比后续柱状图了解到在软件行业当中,上海还是最发达的地方,软件服务业发达。根据我对于杭州人才引入政策的了解
情况下看,杭州的岗位同深圳差距不大,很符合正常情况。
综上,北上广深的岗位需求量依旧十分庞大。由此看出许多人依然向往大城市。同时新一线城市也在发展,如杭州与深圳的岗位和软件相关岗位接近。但目前的电子制造产业深圳依旧更好一些这个对于新一线城市是一个挑战。
五.总结
通过对于一段时间学习爬虫,发现了爬虫正真的难点是能够不受反爬策略影响,自主处理在抓取过程中出现的各种异常不让程序中断运行。在我编写代码的过程中就遇到很多IP代理的异常,需要使用异常处理去处理异常让程序能够不断运行下去。反爬策略也是个难题,对于很多大型网站而言,数据宝贵会得到很好的保护。如何能够破解这也是一种挑战呀。
我自己的程序经常性的运行一段时间就卡死,直到我做了异常处理才能够在IP过期后,花费2分钟的时间解决该问题重置地址池,让程序继续运行。这两分钟主要还是代理IP过期后,自主响应延时。在后续的运行当中,使用质量差的IP地址仍旧会卡死,改用质量好的够连i续抓取几千条页面。