人生苦短,快学Python!
初学scrapy
之后,发现就是效率对比于selenium和requests快了很多,那么问题来了,如果网站设置了反爬,比如User-Agent反爬,cookie反爬,IP封禁等等,所以我们需要通过集成selenium到scrapy中,绕过网站反爬,达到目的。
这里选择智联招聘
网站作为案例,就是虽然不是动态网页,但是它需要模拟登录,所以我们通过scrapy集成selenium进行数据抓取。
一、需求分析
打开目标网站,搜索web前端开发工程师。
这是首页,由于我的当前位置在武汉,所以系统自动定位到武汉,点击搜索后:
这个就是需要通过selenium出路的一个点。
手动登录后得到以下界面:
我们的目标是每一条招聘信息的8条数据:
- name 职位名称
- salary 薪资
- adress 地区
- experience 经验
- eduBack 教育背景
- company 公司名称
- companyType 公司类型
- scale 公司规模
- info 简介
二、scrapy项目文件配置
定义items
代码语言:javascript复制import scrapy
class ZlzpItem(scrapy.Item):
name = scrapy.Field()
***薪资 公司 规模...***
info = scrapy.Field()
定义scrapy爬虫:zl.py(智联)
代码语言:javascript复制#这里先说明下url:
firstPageUrl : 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p=1'
#作为第一页的url,下面的myspider.py中就不在展示,避免代码冗余。
base_url = 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p={}'
然后下面是zl.py的源码:(分为几个部分)
1、初始化设置:
代码语言:javascript复制# -*- coding: utf-8 -*-
import scrapy
from zlzp.items import ZlzpItem
count = 1 # 定义一个全局变量,与base_url构建 下一页的url
class ZlSpider(scrapy.Spider):
name = 'zl'
allowed_domains = ['zhaopin.com']
start_urls = [firstPageUrl]
2、parse函数:
代码语言:javascript复制 def parse(self, response):
global count
count = 1 # 每解析一次页面,让count 1,和baseurl构造下一页的url
jobList = response.xpath('//div[@class="positionlist"]/div/a')
for job in jobList:
name = job.xpath("./div[1]/div[1]/span[1]/text()").extract_first()
...salary***,company***,....
info = job.xpath("./div[3]/div[1]//text()").extract_first()
item = ZlzpItem(name=name,salary=salary,company=company,adress=adress,experience=experience,eduBack=eduBack,companyType=companyType,scale=scale,info=info)
yield item
3、分页:
代码语言:javascript复制 next_url = 'https://sou.zhaopin.com/?jl=736&kw=web前端工程师&p={}'.format(count)
if count == 34:
return None # 设置程序停止的条件
if next_url:
yield scrapy.Request(next_url,callback=self.parse)
定义下载器中间件(DownloadMiddleware):myDownloadMiddleware.py
代码语言:javascript复制from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
...
class ZlzpDownloaderMiddleware:
def __init__(self):
self.driver = webdriver.Chrome()
def process_request(self, request, spider):
self.driver.get(request.url)
time.sleep(3) # 休息3s
# 设置显示等待,由于需要登录,我们手机扫码登录,知道页面出现(即url显示为firstpageurl)
WebDriverWait(self.driver, 1000).until(
EC.url_contains(request.url)
)
time.sleep(6) # 登录成功之后页面需要时间加载出来,再休息几秒
return HtmlResponse(url=self.driver.current_url, body=self.driver.page_source, encoding="utf-8",
request=request) # 然后把这个response对象返回给爬虫(zl.py)
说明:
- selenium集成到scrapy中的核心就是在爬虫中间件中拦截请求,把处理后的响应对象返回,对应于爬虫文件(这里的zl.py)parse函数中的response,如果不集成selenium,那么response对象不能很好应对网站的反爬.
- 此处的parse_request方法中只有少量的selenium代码,因为动态操作其实不多.
- 重点:return后面的response对象:
在这里我们不能return None,如果return None,那么请求会被发送到下载中间件去下载这个页面,在将这个页面的response返回给spider(hr.py)。但是我们上面browser.get的时候就已经下载了这个页面的内容,所以没有必要在下载一次,我们只要制定一个response对象,直接返回这个response给spider即可
定义管道(Pipeline):pipelines.py
代码语言:javascript复制from itemadapter import ItemAdapter
import csv
class ZlzpPipeline:
def __init__(self):
self.f = open('zlJob.csv', 'w', encoding='utf-8', newline='')
# self.file_name = ['name','upTime','salary','needs','welfare','company','scale','types']
self.file_name = ['name','salary','company','adress','experience','eduBack','companyType','scale','info']
self.writer = csv.DictWriter(self.f, fieldnames=self.file_name)
self.writer.writeheader()
def process_item(self, item, spider):
self.writer.writerow(dict(item))# 写入spider传过来的具体数值
return item # 写入完返回
def close_spider(self, spider):
self.f.close()
settings.py配置
代码语言:javascript复制BOT_NAME = 'zlzp'
SPIDER_MODULES = ['zlzp.spiders']
NEWSPIDER_MODULE = 'zlzp.spiders'
LOG_LEVEL = 'WARNING'
......
ROBOTSTXT_OBEY = False
......
DEFAULT_REQUEST_HEADERS = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36',
'Accept': 'text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
......
DOWNLOADER_MIDDLEWARES = {
'zlzp.middlewares.ZlzpDownloaderMiddleware': 543,
}
......
ITEM_PIPELINES = {
'zlzp.pipelines.ZlzpPipeline': 300,
}
......
......表示注释代码,这里省略。
三、程序运行
命令行键入:
代码语言:javascript复制scrapy crawl hr
pic1:运行程序结束到第34页,对应count = 34
pic02:(csv文件)
四、数据简单分析
查看数据
代码语言:javascript复制import pandas as pd
df = pd.read_csv('./zlJob.csv')
df.head()
薪资饼图展示
代码语言:javascript复制c = (
Pie(init_opts=opts.InitOpts(bg_color="white"))
.add("", [list(z) for z in zip(typesX,number)]) # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
.set_global_opts(title_opts=opts.TitleOpts(title="类型:")) # 标题
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # 数据标签设置
)
c.render_notebook()
经验要求柱图展示
代码语言:javascript复制from pyecharts.charts import Bar
bar = Bar()
bar.add_xaxis(['3-5年', '1-3年', '不限', '5-10年', '无经验', '1年以下', '10年以上'])
bar.add_yaxis('经验要求',[462,329,83,78,19,15,4])
bar.render()
学历要求柱图展示
代码语言:javascript复制c = (
Pie(init_opts=opts.InitOpts(bg_color="white"))
.add("", [list(z) for z in zip(educationTypes,number)]) # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
.set_global_opts(title_opts=opts.TitleOpts(title="类型:")) # 标题
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # 数据标签设置
)
c.render_notebook()
大多数要求本科学历,或者说大专及以上学历。
公司类型柱图展示
代码语言:javascript复制c = (
Pie(init_opts=opts.InitOpts(bg_color="white"))
.add("", [list(z) for z in zip(companyTypes,number)]) # zip函数两个部分组合在一起list(zip(x,y))-----> [(x,y)]
.set_global_opts(title_opts=opts.TitleOpts(title="类型:")) # 标题
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}")) # 数据标签设置
)
c.render_notebook()
可以看到大多数公司是民营或者上市公司。
五、总结
页面翻页处理,由于我们只是使用selenium就是打开网页请求数据,所以一般在爬虫文件中进行翻页处理,如果对应的下一页的a标签的href属性不是下一页的页面url,我们需要设置动态全局变量,构建动态的url。
下载中间件中设置的selenium的相关操作,动态点击,页面滚轮操作,显隐式等待等等,重要的是返回的response对象,这个是集成selenimu到scrapy的核心,在下载中间件中拦截请求,把处理后的response对象返回给爬虫。