https://blog.csdn.net/weixin_38818798/article/details/91368604
操作系统:Windows10
浏览器:Chrome 75.0.3770.80(正式版本)(64 位)
开发环境:Anaconda 2019.03(Python3.7)
IDE:PyCharm 2019.1.1 (Professional Edition)
爬虫框架:Scrapy
数据库:MySQL Community 8.0.16.0
爬取目标:腾讯招聘全部职位
步骤一:搭建开发环境
1、下载安装Anaconda
下载地址:https://www.anaconda.com/distribution/
下载完成后双击“Anaconda3-2019.03-Windows-x86_64”进行安装
2、下载安装PyCharm
官网地址:http://www.jetbrains.com/
进入官网找到Download相关页面进行下载,由于我对PyCharm进行了PJ,在hosts文件中设置了禁止访问http://www.jetbrains.com/,无法打开官网,下载过程就不进行截图演示了。
除了官网下载,也可以到软件学堂进行下载。
下载地址:http://www.xue51.com/search.asp?wd=pycharm
可在其中自行选择版本,里面安装及PJ方法写得很详细,在此就不再赘述了。
3、下载安装MySQL
下载地址:https://cdn.mysql.com//Downloads/MySQLInstaller/mysql-installer-community-8.0.16.0.msi
下载后双击运行“mysql-installer-community-8.0.16.0.msi”,一路默认直接点击下一步即可,其中设置好自己的root密码
4、安装Scrapy
在windows开始菜单中找到“Anaconda3(64-bit)”点击其中的“Anaconda Prompt”打开Anaconda命令行。
在Anaconda命令行中输入:pip install scrapy进行安装。
5、安装twisted
安装方法同上,在Anaconda命令行中输入:pip install twisted进行安装。
6、安装pymysql
安装方法同上,在Anaconda命令行中输入:pip install pymysql进行安装。
至此,开发环境搭建完成
步骤二:创建爬虫项目
1、为了便于项目管理,我们先在"C:\Users\用户名\"中新建一个文件夹,我创建的文件夹是“MyProjects”
2、打开Anaconda命令行输入:cd MyProjects进入“MyProjects”文件夹
3、创建名为tencent的爬虫项目,输入:scrapy startproject tencent
4、输入:cd tencent进入项目文件夹
5、创建名为tencentRecruit的爬虫,输入:scrapy genspider tencentRecruit tencent.com
至此,爬虫项目创建完成
步骤三:在PyCharm中打开爬虫项目,开始开发爬虫
1、打开PyCharm,点击菜单栏的“File”->"Open..."
项目打开后如上图所示,Scrapy的项目结构如下所示
其中run.py原本是没有的,此文件是自己写的,目的是可以在scrapy中直接运行爬虫程序。
scrapy.cfg是爬虫项目的配置文件,一般情况不需要修改。
spiders文件夹用于存放爬虫的主程序,目前里面已经有了之前我们通过命令行创建的爬虫tencentRecruit.py。
items.py是爬虫项目的目标文件。
pipelines.py是爬虫项目的管道文件,用于对爬取到的数据进行处理(如:数据格式化、将数据写入文件、将数据写入数据库)。
settings.py是爬虫项目的设置文件,用于设置爬虫。
2、创建run.py文件,编写入口程序
-
from scrapy
import cmdline
-
-
if __name__ ==
'__main__':
-
cmdline.execute(
'scrapy crawl tencentRecruit'.split())
3、修改settings.py文件,对爬虫进行设置
scrapy已经以注释的形式为我们写好了几乎所有会用到的设置
我们只要将需要用的设置取消注释即可,修改后的settings.py文件如下
-
# -*- coding: utf-8 -*-
-
-
# Scrapy settings for tencent project
-
#
-
# For simplicity, this file contains only settings considered important or
-
# commonly used. You can find more settings consulting the documentation:
-
#
-
# https://doc.scrapy.org/en/latest/topics/settings.html
-
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
-
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
-
-
BOT_NAME =
'tencent'
-
-
SPIDER_MODULES = [
'tencent.spiders']
-
NEWSPIDER_MODULE =
'tencent.spiders'
-
-
# Crawl responsibly by identifying yourself (and your website) on the user-agent
-
# 此处取消注释,并将源文件的内容修改为浏览器的USER-AGENT,设置USER-AGENT的作用是防止反爬
-
USER_AGENT =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 ' \
-
'Safari/537.36 '
-
-
# Obey robots.txt rules
-
# 此处取消注释,并将值设置为False,作用是让爬虫程序不遵守目标网站的robots规则,robots规则用于规定网站内那些目录允许爬虫爬取,那些目录不允许爬虫爬取
-
ROBOTSTXT_OBEY =
False
-
-
# Configure maximum concurrent requests performed by Scrapy (default: 16)
-
# 此处取消注释,使爬虫程序可以进行并发爬取,提高爬取效率
-
CONCURRENT_REQUESTS =
32
-
-
# Configure a delay for requests for the same website (default: 0)
-
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
-
# See also autothrottle settings and docs
-
# DOWNLOAD_DELAY = 3
-
# The download delay setting will honor only one of:
-
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
-
# CONCURRENT_REQUESTS_PER_IP = 16
-
-
# Disable cookies (enabled by default)
-
# 此处取消注释,作用是禁用COOKIES,用于防止反爬,需要注意的是:如果要爬取的内容不需要登陆即可禁用COOKIES,如果要爬取的内容需要登陆,不能禁用COOKIES,我们爬取的网站内容不需要登陆,所以此处设置禁用COOKIES
-
COOKIES_ENABLED =
False
-
-
# Disable Telnet Console (enabled by default)
-
# TELNETCONSOLE_ENABLED = False
-
-
# Override the default request headers:
-
# DEFAULT_REQUEST_HEADERS = {
-
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
-
# 'Accept-Language': 'en',
-
# }
-
-
# Enable or disable spider middlewares
-
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
-
# SPIDER_MIDDLEWARES = {
-
# 'tencent.middlewares.TencentSpiderMiddleware': 543,
-
# }
-
-
# Enable or disable downloader middlewares
-
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
-
# DOWNLOADER_MIDDLEWARES = {
-
# 'tencent.middlewares.TencentDownloaderMiddleware': 543,
-
# }
-
-
# Enable or disable extensions
-
# See https://doc.scrapy.org/en/latest/topics/extensions.html
-
# EXTENSIONS = {
-
# 'scrapy.extensions.telnet.TelnetConsole': None,
-
# }
-
-
# Configure item pipelines
-
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
-
# ITEM_PIPELINES = {
-
# 'tencent.pipelines.TencentPipeline': 300,
-
# }
-
-
# Enable and configure the AutoThrottle extension (disabled by default)
-
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
-
# AUTOTHROTTLE_ENABLED = True
-
# The initial download delay
-
# AUTOTHROTTLE_START_DELAY = 5
-
# The maximum download delay to be set in case of high latencies
-
# AUTOTHROTTLE_MAX_DELAY = 60
-
# The average number of requests Scrapy should be sending in parallel to
-
# each remote server
-
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
-
# Enable showing throttling stats for every response received:
-
# AUTOTHROTTLE_DEBUG = False
-
-
# Enable and configure HTTP caching (disabled by default)
-
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
-
# HTTPCACHE_ENABLED = True
-
# HTTPCACHE_EXPIRATION_SECS = 0
-
# HTTPCACHE_DIR = 'httpcache'
-
# HTTPCACHE_IGNORE_HTTP_CODES = []
-
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
4、分析目标网站
目标网站:https://careers.tencent.com/
进入这个地址看到首页如下
在右上角有个“查看工作岗位>”,我们点进去并用开发者工具看一下
我们看到了工作岗位的列表页,也看到了显示规则:每页显示10个岗位,共3970个岗位。
看到此页面我们的爬取策略已经清楚了,有列表页自然会有详情页,如果我们想获取到岗位的详细信息,我们需要点击岗位进入岗位详情页。所以我们需要以深度优先的模式进行水平爬取和垂直爬取。
深度优先:当爬虫在当前页面获取到第一个详情页的链接时,马上进入到这个链接去爬取详情页面的内容,爬取完毕后再回到列表页继续获取下一个链接,并马上进入到链接页面爬取页面内容,依此反复执行。
广度优先:当爬虫在当前页面获取到第一个详情页的链接时,先不进入到这个链接去爬取链接页面的内容,而是继续获取下一个链接,直到获取到所有的链接后再依次进入链接页面进行爬取。
水平爬取:不停的翻页爬取所有的岗位列表,目的是从中获取到每一个岗位的详情页地址。
垂直爬取:通过水平爬取获取到的详情页地址进入到详情页去获取每一个岗位的详细信息。
打开开发者工具,找到了第一条招聘职位的标签
但是!!!观察发现<a>标签中没有任何的地址,这意味着我们无法获取到详情页的链接地址,记住这个问题,一会儿再想办法解决,我们继续分析这个列表页,先搞定水平爬取。
接下来我们翻页来看一下url的变化
我们发现点击下方的翻页后url地址中多了个?index=2,并且每次翻页只有这个数字在变,其它无任何变化,说明这个?index=后面的数字就是页码,我们通过改变这个数字就可以跳转到相应的页面,这样我们就可以遍历每一页的列表获取到每一个岗位的详情页链接地址。
事情真的如此简单吗?
我们使用scrapy的scrapy shell来试一下,打开命令行,输入:scrapy shell https://careers.tencent.com/search.html?index=2
这时我们就可以开始获取标签内容了。
以第二页的第一个岗位为例,在开发者工具中,我们看到岗位名称是在一个<h4>标签中,并且这个<h4>标签有class,那么我们就先试一下看看能否取到这个岗位名称。
什么也没获取到。。。那么我们再来看看response里都拿到了些什么
我们看到整个response就拿到了这些东西,由此说明,果然没有那么简单。。。页面是动态生成的。。。
接下来就要开始对付动态生成的页面了
首先两个问题出现了:
为什么浏览器就可以看到正常的页面?
为什么response得到的源码与浏览器里刚才看到的不一样?
其实浏览器中得到的源码跟我们的response得到的是一样的,我们在浏览器中点击鼠标右键,然后点击“查看网页源代码”
看到没?是完全一样的,也是这样的。
问题又来了:
这么点东西怎么显示出的那么多内容的页面?
这个怎么跟开发者工具中看到的不一样啊?
这就是动态加载,我们向服务器发出请求,服务器收到后给我们发来响应,但这个网站发回来的响应就是js程序,浏览器是可以解析和加载js代码的,而页面就是通过这些js程序创建的,这样浏览器就可以把加载好的页面呈现给我们,所以开发者工具的Elements选项卡中所呈现的是浏览器解读了js程序之后生成出来的页面。我们的scrapy中的Request是无法加载js代码的,它只能拿到js代码,像浏览器一样,但是无法像浏览器一样把这些代码解读加载出来给我们。
好了,知道了这些,我们又如何把它加载出来呢?
有三个方案可以解决这个问题:
第一、使用Selenium+PhantomJS来加载这些js(这可能是很多人一上来的第一个想法)
来说一下Selenium是什么,Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。它支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等浏览器。如果用这个东西,那么爬虫在爬取页面的时候会自动打开一个浏览器,然后从这个浏览器中读取所需要的数据。
再来说下PhantomJS是什么,PhantomJS是一个基于webkit的javascript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码,它是个隐形的浏览器,也就是它是没有任何界面的,就像命令行一样在后台运行,不会打开或弹出任何东西。
使用Selenium+PhantomJS的目的就是为了不弹出浏览器。
需要提及的一点:目前PhantomJS官方已经在官网发出了暂停维护的公告。
这个方案是可行的,可以解决问题,但是它为程序增加了负担,爬取的效率也慢了很多,因为它需要等待浏览器加载。这显然不是我们的首选方案。
优点:由于它是通过浏览器去获取和解析数据再传回给程序,所以基本实现了可见即可爬。(为什么说是基本呢?因为很多网站想出了新的方法来反Selenium爬虫,比如:现在的斗鱼直播列表,如果用Selenium+PhantomJS爬取的话是无法实现点击翻页的,因为Selenium+PhantomJS的鼠标点击事件是有限的,不是所有的标签都可以使用点击事件,另外,目前斗鱼的翻页连<a>标签都不是,是个<li>,里面是纯文字,模拟点击不会有任何反应。当然遇到这个问题我们还有其它的解决方案,由于此文未涉及,所以不进行深入。)
缺点:运行效率很低,大幅降低爬取速度。
第二、使用requests新出的requests-html
requests是一个很强大的库,在开发爬虫的过程中,如果我们不使用像scrapy这样的框架的的话,那么很有可能首选就是requests了,它对我们开发爬虫所需要的库进行了很好的封装。requests-html在原有基础上加入了加载javascript的功能,使得它也可以解析加载js程序了,但是它同样也是加载了一个无边浏览器,原理和Selenium+PhantomJS是一样的。显然,这个方案也不是我们的首选方案。
优点:由于它是通过浏览器去获取和解析数据再传回给程序,所以基本实现了可见即可爬。(为什么说是基本呢?因为很多网站想出了新的方法来反Selenium爬虫,比如:现在的斗鱼直播列表,如果用Selenium+PhantomJS爬取的话是无法实现点击翻页的,因为Selenium+PhantomJS的鼠标点击事件是有限的,不是所有的标签都可以使用点击事件,另外,目前斗鱼的翻页连<a>标签都不是,是个<li>,里面是纯文字,模拟点击不会有任何反应。当然遇到这个问题我们还有其它的解决方案,由于此文未涉及,所以不进行深入。)
缺点:运行效率很低,大幅降低爬取速度。
第三、继续分析网站,找到真实接口,直接拿到json数据
优点:不影响爬取速度,拿到的json数据解析也要比网页标签更方便快捷。
缺点:分析网站比较费精力。
从爬取效率的角度这显然是我们的首选方案了!
接下来我们继续看一下页面
我们先点击开发者工具中“1”处的Network选项卡,看到“2”处有这样一条请求,点击“2”处的请求后,我们看到“3”处服务器响应的直接是一个json数据,内容与我们在页面上看到的是一样的。我们在点击一下“3”处的“Headers”选项卡
在“Headers”选项卡中我们看到了“Request URL”,这个就是请求的真实api了,我们把它复制下来
我们去掉地址中无用的参数来简化一下url
简化后的URL:https://careers.tencent.com/tencentcareer/api/post/Query?pageIndex=1&pageSize=10
我们把简化后的URL输入到浏览器中测试一下简化后的URL是否可以获取到数据
测试成功,可以看到我们已经获取到了第一页的json数据,翻页只需要更换&pageIndex=后面的页码即可。
至此,网站的分析工作完成了。
5、根据json数据我们将字段设计好,我们发现在这个json中并没有我们需要的详情页的链接地址,也没有岗位要求,没关系,我们一步一步来,先把列表页中我们认为有用的数据爬下来。
我们需要的数据字段:
岗位ID:PostId
职位ID:RecruitPostId
职位名称:RecruitPostName
工作地点:LocationName
职位类别:CategoryName
职责:Responsibility
发布日期:LastUpdateTime
将这些字段写入scrapy爬虫项目的items文件中,完整代码如下:
-
# -*- coding: utf-8 -*-
-
-
# Define here the models for your scraped items
-
#
-
# See documentation in:
-
# https://doc.scrapy.org/en/latest/topics/items.html
-
-
import scrapy
-
-
-
class TencentItem(scrapy.Item):
-
# define the fields for your item here like:
-
PostId = scrapy.Field()
-
RecruitPostId = scrapy.Field()
-
RecruitPostName = scrapy.Field()
-
LocationName = scrapy.Field()
-
CategoryName = scrapy.Field()
-
Responsibility = scrapy.Field()
-
LastUpdateTime = scrapy.Field()
6、开始写爬虫程序
我们首先实现水平爬取,也就是将整个岗位列表爬取下来。
首先拿出我们刚才简化过的URL
https://careers.tencent.com/tencentcareer/api/post/Query?pageIndex=1&pageSize=10
我们要实现自动翻页,所以地址中的pageIndex不能写死,我们需要将页码从URL中剥离出来,在程序中动态生成页码之后再拼接到URL中。
为了拼接方便少些点东西,我们将简化的URL再修改一下,把pageIndex和pageSize调换一下位置并去掉页码
修改后的URL:https://careers.tencent.com/tencentcareer/api/post/Query?pageSize=10&pageIndex=
因为我们的项目是实现了水平和垂直爬取的,之前创建好的爬虫我们留给完成的程序。为了由简入繁的完成此文,这里我们先新建一个爬虫单独实现一下水平爬取,来体会一下scrapy爬虫。
打开命令行,进入tencent项目文件夹,输入:scrapy genspider tencentJobList tencent.com
之后我们可以看到PyCharm中的spiders文件夹中多了一个tencentJobList.py文件,接下来我们就开始在这个文件中编写我们的水平爬取爬虫,完整代码如下:
-
# -*- coding: utf-8 -*-
-
import json
-
-
import scrapy
-
-
-
class TencentjoblistSpider(scrapy.Spider):
-
name =
'tencentJobList'
-
allowed_domains = [
'tencent.com']
-
-
url =
'https://careers.tencent.com/tencentcareer/api/post/Query?pageSize=10&pageIndex='
-
-
# 设置页码
-
offset =
1
-
# 记录爬取数量
-
num =
0
-
-
start_urls = [url + str(offset)]
-
-
def parse(self, response):
-
# 这里判断翻页我使用try的方式,比如:如果一共有100页,那么https://careers.tencent.com/tencentcareer/api/post/Query?pageSize=10&pageIndex=101这个地址是不存在的,也就无法返回任何数据,下面的程序就会报错
-
try:
-
# 获取json内容
-
content = json.loads(response.text)
-
# 获取岗位列表
-
jobs = content[
'Data'][
'Posts']
-
# 便利岗位列表获取每个岗位
-
for job
in jobs:
-
# 获取岗位ID
-
PostId = job[
'PostId']
-
# 获取职位ID
-
RecruitPostId = job[
'RecruitPostId']
-
# 获取岗位名称
-
RecruitPostName = job[
'RecruitPostName']
-
# 获取工作地点
-
LocationName = job[
'LocationName']
-
# 获取岗位类别
-
CategoryName = job[
'CategoryName']
-
# 获取职责
-
Responsibility = job[
'Responsibility']
-
# 获取发布日期
-
LastUpdateTime = job[
'LastUpdateTime']
-
print(
'岗 位 ID:', PostId)
-
print(
'职 位 ID:', RecruitPostId)
-
print(
'岗位名称:', RecruitPostName)
-
print(
'工作地点:', LocationName)
-
print(
'岗位类别:', CategoryName)
-
print(
'岗位职责:', Responsibility)
-
print(
'发布日期:', LastUpdateTime)
-
print(
'-----------------------------------------------------------------------------------------------------------------------------------------------')
-
# 将页码加1
-
self.offset +=
1
-
# 拼接新的页面URL并向服务器发出请求,使用回调函数调用自身获取新页面的相关数据
-
yield scrapy.Request(self.url + str(self.offset), callback=self.parse)
-
# 抛出异常,如果请求的页面不存在,则抛出异常并显示爬取结束终止程序
-
except TypeError:
-
print(
'爬取结束')
修改run.py文件,让PyCharm可以运行我们新创建的爬虫,完整代码如下:
-
from scrapy
import cmdline
-
-
if __name__ ==
'__main__':
-
cmdline.execute(
'scrapy crawl tencentJobList'.split())
-
# cmdline.execute('scrapy crawl tencentRecruit'.split())
这里提示一下:在PyCharm中运行爬虫其实就是用PyCharm调用命令行,使用os库的os.system()同样可以调用命令行执行命令,比如这段代码可以换成:
-
import os
-
-
if __name__ ==
'__main__':
-
os.system(
'scrapy crawl tencentJobList')
但这里不建议这样写,建议使用cmdline.execute(),因为使用cmdline.execute()可以在PyCharm中对爬虫进行Debug(断点调试),在复杂的爬虫项目中方便程序调试。如果使用os.system()的话即便程序中设置了断点,Debug运行之后程序依然会一口气执行完,断点不会起任何作用。
好了,现在我们运行run.py,运行后可以看到如下图所示
至此,我们已经完成了岗位列表页所有数据的水平爬取。
接下来我们开始研究垂直爬取,通过从列表页获取到的数据继续获取每一个招聘岗位的详细信息。
我们按照上面的网站分析的思路继续分析一下岗位详情页,我们先随便进入一个详情页
我们看到URL地址是:https://careers.tencent.com/jobdesc.html?postId=1137985610095529984
分析这个URL的结构,协议://域名/岗位页面?岗位ID
看似很合理也很简单,但事实真的如此吗?有了之前的经验,我们还是先看下源码吧
果然,跟之前情况一样,这个URL是假的。。。。。我们按照之前的思路去开发者工具的Network选项卡看一下
我们发现了两个有真实数据的请求
和
其中第一个是我们需要的,就是岗位详情页请求api,地址结构中的ByPostId,意思就是通过岗位ID获取数据。
第二个是本页中下面的相关推荐栏目的数据,我们不需要。
接下来仍然是简化URL
简化后的URL:https://careers.tencent.com/tencentcareer/api/post/ByPostId?postId=1137985610095529984
我们到浏览器中测试一下简化后的URL
没问题,同样是直接获取到了json数据,我们看到之前的所有数据项和之前数据项中没有的岗位要求都在详情页中体现了。
之前提到的,<a>标签没有地址无法获取详情页的问题就此也解决了,我们直接使用刚找到的api,去掉后面的ID,在程序中动态拼接上岗位ID就可以获取到岗位的详情页了,根本不需要什么<a>标签地址了。如此看来我们在水平爬取列表页的时候只保存一个PostID就可以了,其它数据,我们垂直爬取的时候从详情页中获取并保存即可。
接下来修改我们的代码,此时需要修改的文件有两个
一个是items.py,我们要在里面加上一个Requirement数据项,修改后的完整代码:
-
# -*- coding: utf-8 -*-
-
-
# Define here the models for your scraped items
-
#
-
# See documentation in:
-
# https://doc.scrapy.org/en/latest/topics/items.html
-
-
import scrapy
-
-
-
class TencentItem(scrapy.Item):
-
# define the fields for your item here like:
-
PostId = scrapy.Field()
-
RecruitPostId = scrapy.Field()
-
RecruitPostName = scrapy.Field()
-
LocationName = scrapy.Field()
-
CategoryName = scrapy.Field()
-
Responsibility = scrapy.Field()
-
Requirement = scrapy.Field()
-
LastUpdateTime = scrapy.Field()
另一个需要修改的文件就是我们最开始创建好的tencentRecruit.py,我们首先要将水平爬取的代码复制到tencentRecruit.py并进行修改,去掉无用的部分,然后进行垂直爬取代码的编写,修改后的完整代码:
-
# -*- coding: utf-8 -*-
-
import json
-
-
import scrapy
-
-
from tencent.items
import TencentItem
-
-
-
class TencentrecruitSpider(scrapy.Spider):
-
name =
'tencentRecruit'
-
allowed_domains = [
'tencent.com']
-
# 要爬取的URL
-
url =
'https://careers.tencent.com/tencentcareer/api/post/Query?pageSize=10&pageIndex='
-
-
# 设置页码
-
offset =
1
-
# 记录爬取数量
-
num =
0
-
# 拼接起始URL
-
start_urls = [url + str(offset)]
-
-
def parse(self, response):
-
# 这里判断翻页我使用try的方式,比如:如果一共有100页,那么https://careers.tencent.com/tencentcareer/api/post/Query?pageSize=10&pageIndex=101这个地址是不存在的,也就无法返回任何数据,下面的程序就会报错
-
try:
-
# 获取列表页json内容
-
content = json.loads(response.text)
-
# 获取岗位列表
-
jobs = content[
'Data'][
'Posts']
-
# 遍历岗位列表,获取每个岗位的PostId
-
for job
in jobs:
-
# 将找到的详情页的api与岗位ID进行拼接,得到岗位详情页的完整URL
-
link =
'https://careers.tencent.com/tencentcareer/api/post/ByPostId?postId=' + job[
'PostId']
-
# 使用详情页面的URL向服务器发出请求,使用回调函数调用parse_data解析详情页新页面的相关数据
-
yield scrapy.Request(link, callback=self.parse_data)
-
# 将页码加1
-
self.offset +=
1
-
# 拼接新的页面URL并向服务器发出请求,使用回调函数调用自身解析列表页新页面的相关数据
-
yield scrapy.Request(self.url + str(self.offset), callback=self.parse)
-
# 抛出异常,如果请求的页面不存在,则抛出异常并显示爬取结束终止程序
-
except TypeError:
-
print(
'爬取结束')
-
-
def parse_data(self, response):
-
# 获取详情页json内容
-
content = json.loads(response.text)
-
# 获取岗位详情数据
-
details = content[
'Data']
-
# 实例化items.py中的TencentItem()
-
item = TencentItem()
-
# 向item中装入数据
-
item[
'PostId'] = details[
'PostId']
-
item[
'RecruitPostId'] = details[
'RecruitPostId']
-
item[
'RecruitPostName'] = details[
'RecruitPostName']
-
item[
'LocationName'] = details[
'LocationName']
-
item[
'CategoryName'] = details[
'CategoryName']
-
item[
'Responsibility'] = details[
'Responsibility'].replace(
'\r',
'').replace(
'\n',
'').strip()
-
item[
'Requirement'] = details[
'Requirement'].replace(
'\r',
'').replace(
'\n',
'').strip()
-
item[
'LastUpdateTime'] = details[
'LastUpdateTime']
-
# 爬取数量加1
-
self.num +=
1
-
print(item)
-
print(
'爬取到第%d个招聘信息' % self.num)
-
yield item
至此,已经完成了爬虫的编写,我们修改run.py文件运行爬虫,修改后的完整代码:
-
from scrapy
import cmdline
-
-
if __name__ ==
'__main__':
-
# cmdline.execute('scrapy crawl tencentJobList'.split())
-
cmdline.execute(
'scrapy crawl tencentRecruit'.split())
运行run.py后,看到如下所示
接下来,我们将获取到的数据存入MySQL数据库中
我们使用安装MySQL时自带的MySQL Workbench 8.0 CE创建数据库及数据表
在空白处点击鼠标右键,然后点击“Create Schema...”
输入数据库名称后点击“Apply”数据库就创建好了,接下来创建数据表
在此处点击鼠标右键,之后点击“Create Table”创建数据表
输入数据表名并创建相应字段后点击“Apply”创建数据表
修改scrapy爬虫项目中的“settings.py”文件,添加MySQL数据库设置,取消ITEM_PIPELINES注释,并添加一个设置
-
MYSQL_HOST =
"127.0.0.1"
-
MYSQL_PORT =
3306
-
MYSQL_DBNAME =
"tencent"
-
MYSQL_USER =
"root"
-
MYSQL_PASSWORD =
"*******"
-
ITEM_PIPELINES = {
-
'tencent.pipelines.TencentPipeline':
300,
-
'tencent.pipelines.MysqlTwistedPipeline':
300,
-
}
修改后的settings.py文件的完整代码:
-
# -*- coding: utf-8 -*-
-
-
# Scrapy settings for tencent project
-
#
-
# For simplicity, this file contains only settings considered important or
-
# commonly used. You can find more settings consulting the documentation:
-
#
-
# https://doc.scrapy.org/en/latest/topics/settings.html
-
# https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
-
# https://doc.scrapy.org/en/latest/topics/spider-middleware.html
-
-
BOT_NAME =
'tencent'
-
-
SPIDER_MODULES = [
'tencent.spiders']
-
NEWSPIDER_MODULE =
'tencent.spiders'
-
-
# Crawl responsibly by identifying yourself (and your website) on the user-agent
-
USER_AGENT =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 ' \
-
'Safari/537.36 '
-
-
# Obey robots.txt rules
-
ROBOTSTXT_OBEY =
False
-
-
# Configure maximum concurrent requests performed by Scrapy (default: 16)
-
CONCURRENT_REQUESTS =
32
-
-
# Configure a delay for requests for the same website (default: 0)
-
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
-
# See also autothrottle settings and docs
-
# DOWNLOAD_DELAY = 3
-
# The download delay setting will honor only one of:
-
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
-
# CONCURRENT_REQUESTS_PER_IP = 16
-
-
# Disable cookies (enabled by default)
-
COOKIES_ENABLED =
False
-
-
# Disable Telnet Console (enabled by default)
-
# TELNETCONSOLE_ENABLED = False
-
-
# Override the default request headers:
-
# DEFAULT_REQUEST_HEADERS = {
-
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
-
# 'Accept-Language': 'en',
-
# }
-
-
# Enable or disable spider middlewares
-
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
-
# SPIDER_MIDDLEWARES = {
-
# 'tencent.middlewares.TencentSpiderMiddleware': 543,
-
# }
-
-
# Enable or disable downloader middlewares
-
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
-
# DOWNLOADER_MIDDLEWARES = {
-
# 'tencent.middlewares.TencentDownloaderMiddleware': 543,
-
# }
-
-
# Enable or disable extensions
-
# See https://doc.scrapy.org/en/latest/topics/extensions.html
-
# EXTENSIONS = {
-
# 'scrapy.extensions.telnet.TelnetConsole': None,
-
# }
-
-
# Configure item pipelines
-
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
-
ITEM_PIPELINES = {
-
'tencent.pipelines.TencentPipeline':
300,
-
'tencent.pipelines.MysqlTwistedPipeline':
300,
-
}
-
-
# Enable and configure the AutoThrottle extension (disabled by default)
-
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
-
# AUTOTHROTTLE_ENABLED = True
-
# The initial download delay
-
# AUTOTHROTTLE_START_DELAY = 5
-
# The maximum download delay to be set in case of high latencies
-
# AUTOTHROTTLE_MAX_DELAY = 60
-
# The average number of requests Scrapy should be sending in parallel to
-
# each remote server
-
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
-
# Enable showing throttling stats for every response received:
-
# AUTOTHROTTLE_DEBUG = False
-
-
# Enable and configure HTTP caching (disabled by default)
-
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
-
# HTTPCACHE_ENABLED = True
-
# HTTPCACHE_EXPIRATION_SECS = 0
-
# HTTPCACHE_DIR = 'httpcache'
-
# HTTPCACHE_IGNORE_HTTP_CODES = []
-
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
-
-
MYSQL_HOST =
"127.0.0.1"
-
MYSQL_PORT =
3307
-
MYSQL_DBNAME =
"tencent"
-
MYSQL_USER =
"root"
-
MYSQL_PASSWORD =
"******"
修改items.py文件,在items.py中添加一个sql语句函数,修改后的完整代码如下:
-
# -*- coding: utf-8 -*-
-
-
# Define here the models for your scraped items
-
#
-
# See documentation in:
-
# https://doc.scrapy.org/en/latest/topics/items.html
-
-
import scrapy
-
-
-
class TencentItem(scrapy.Item):
-
# define the fields for your item here like:
-
PostId = scrapy.Field()
-
RecruitPostId = scrapy.Field()
-
RecruitPostName = scrapy.Field()
-
LocationName = scrapy.Field()
-
CategoryName = scrapy.Field()
-
Responsibility = scrapy.Field()
-
Requirement = scrapy.Field()
-
LastUpdateTime = scrapy.Field()
-
-
def get_insert_sql(self):
-
# sql语句,用与向数据库中插入数据,此处我没有使用insert into,而是使用的replace into
-
# replace into是向数据库中插入数据时,根据主键进行判断,如果数据已经存在则先删除掉原有数据再插入新的数据,如果数据不存在则直接进行插入
-
# 由于考虑到招聘网站有可能会修改招聘岗位的详情内容,所以此处使用replace into进行插入,以保证所有数据都是最新的
-
insert_sql =
'replace into jobs(PostId, RecruitPostId, RecruitPostName, LocationName, CategoryName, Responsibility, Requirement, LastUpdateTime) values (%s, %s, %s, %s, %s, %s, %s, %s)'
-
params = (
-
self[
'PostId'],
-
self[
'RecruitPostId'],
-
self[
'RecruitPostName'],
-
self[
'LocationName'],
-
self[
'CategoryName'],
-
self[
'Responsibility'],
-
self[
'Requirement'],
-
self[
'LastUpdateTime']
-
)
-
return insert_sql, params
进入最后一步,编写pipelines.py文件,创建MysqlTwistedPipline类,用于将item中的数据存储到MySQL数据库中
此处我们使用Twisted库实现数据库的异步存储,pipelines.py完整代码:
-
# -*- coding: utf-8 -*-
-
-
# Define your item pipelines here
-
#
-
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
-
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
-
from twisted.enterprise
import adbapi
-
-
-
class TencentPipeline(object):
-
def process_item(self, item, spider):
-
return item
-
-
-
class MysqlTwistedPipeline(object):
-
def __init__(self, dbpool):
-
self.dbpool = dbpool
-
-
@classmethod
-
def from_settings(cls, settings):
-
# 获取settings文件中的数据库设置
-
dbparms = {
-
'host': settings[
'MYSQL_HOST'],
-
'port': settings[
'MYSQL_PORT'],
-
'db': settings[
'MYSQL_DBNAME'],
-
'user': settings[
'MYSQL_USER'],
-
'passwd': settings[
'MYSQL_PASSWORD'],
-
'charset':
'utf8mb4',
-
'use_unicode':
True,
-
}
-
# 使用twisted的adbapi函数连接数据库
-
dbpool = adbapi.ConnectionPool(
'pymysql', **dbparms)
-
return cls(dbpool)
-
-
def process_item(self, item, spider):
-
# 调用do_insert函数将item中的数据存入数据库
-
query = self.dbpool.runInteraction(self.do_insert, item)
-
query.addErrback(self.handle_error, item, spider)
-
-
def handle_error(self, failure, item, spider):
-
# 打印错误信息
-
print(failure)
-
-
def do_insert(self, cursor, item):
-
# 从item中获取sql语句
-
insert_sql, params = item.get_insert_sql()
-
# 执行数据库操作
-
cursor.execute(insert_sql, params)
至此,大功告成!运行run.py,待程序结束后所有数据已成功保存到MySQL数据库中(提示:运行前建议注释掉tencentRecruit.py中parse_data()函数中的print(item),这样速度会快很多)
最后说明:Scrapy框架为我们提供了几种爬虫模板,本文中我们使用的是最基本的模板。其实Scrapy中有另一个爬虫模板是直接应对大规模整站爬虫的,需要进行水平和垂直爬取的时候我们一般会直接使用CrawlSpider。以下是两个模板的对比
之后我们其实可以将本例改用CrawlSpider再写一下,其它文件都不用动只是新建一个CrawlSpider模板的爬虫把之前的Spider模板的爬虫重写以下就可以了。由于本文的篇幅已经非常的长了,本文就不进行详细介绍了。本文我们已经使用Scrapy框架开发了一个并发爬虫,并且完全不用我们自己写多线程和去重的代码,我们只是在scrapy框架的settings.py文件中开启了并发功能就实现了并发爬虫,最后也通过Twisted很容易的完成了数据库的异步存储,在大多数情况下这已经是一个非常高效的模式了。如果想进一步提高爬取效率,Scrapy还可以结合Redis很容易的实现分布式爬虫,由于本文示例数据量太小了,并且分布式需要真实的多服务器且多ip才能发挥出最大的效果,单服务器多代理ip和单ip服务器集群都是无法形成真正的分布式爬取的,单ip的服务器集群只适用于分布式计算。另外效率也是有平衡点的,爬取效率提高的同时也要数据库的存储效率跟得上,比如即便使用了Scrapy-Redis实现了分布式,如果需要将数据最终跨域存储到MySQL的话,爬取的速度是会超过数据库存储速度的,运行起来的情况就是,需要爬取的内容早就爬完了,数据库的工作还远远没有完成。本人之前使用本例做过一个测试,数据爬回来之后存入到远程MySQL数据库中,结果,从控制台看爬虫已经把所有数据爬完了,但程序依然没有终止,打开数据库COUNT(*)了一下,发现数据数量与控制台中爬取的数据数量查了好多,运行了几下COUNT(*)发现数据正在陆续继续存储中,最后数据库存储完毕后爬虫程序终止了。
本文原创,码字不易,如需转载请注明出处,谢谢