五大组件
爬虫
中央引擎
请求调度器
下载器
数据队列
三大对象
request对象
response对象
item对象
两大中间件
下载中间件
爬虫中间件
中央引擎从爬虫拿到第一个URL,发送给请求调度器
请求调度器处理成request对象, 返回给中央引擎
中央引擎把request对象发送给下载器(通过下载中间件)
下载器从服务器下载数据后, 处理成response对象, 返回给中央引擎
中央引擎把response对象交给爬虫, 提取数据/ 提取URL(通过爬虫中间件)
爬虫把提取到的数据(或URL),封装成item对象, 返回给中央引擎
如果是数据, 交给数据队列, 保存数据
项目初始化
创建scrapy项目:
scrapy startproject 项目名
创建爬虫:
cd 项目名
,scrapy genspider [ -t 模板名称] 爬虫名 爬虫的域名
把第一个请求URL, 复制到start_urls中
class ItinfoSpider(scrapy.Spider):
name = 'itinfo'
allowed_domains = ['itcast.cn']
start_urls = ['http://www.itcast.cn/channel/teacher.shtml#ac'] # 这里是第一个URL(启动URL)
定义模型, 防止key写错
class ItcastItem(scrapy.Item):
name = scrapy.Field()
level = scrapy.Field()
description = scrapy.Field()
编写爬虫
提取完数据以后封装成
数据模型对象item
,并使用yield
提交给引擎, 引擎会转交给管道处理对于使用xpath()提取的数据, 先提取每一行的数据, 再从每一行里面提取每一个数据
对于class不同的情况, 使用
css样式选择器
,".even,.odd"
(逗号隔开)extract()
从Selector对象提取内容,extract_first()
是从SelectList对象提取内容解析函数
parse()
本身就有xpath, 可以直接提取, 返回的是SelectorList(Selector对象列表)xpath()返回的是selector对象列表, 使用
extract_first()
拿到第一个值xpath()返回的是Selecto对象, 使用
extract()
获取内容直接通过标签内容提取:
response.xpath('//a[text()="下一页"]')
import scrapy
from itcast.items import ItcastItem
class ItinfoSpider(scrapy.Spider):
name = 'itinfo'
allowed_domains = ['itcast.cn']
start_urls = ['http://www.itcast.cn/channel/teacher.shtml#ac'] # 这时第一个URL(启动URL)
def parse(self, response):
# 使用一次xpath, 获取上一级, 不直接获取名字
div_list = response.xpath('//div[@class="li_txt"]')
for div in div_list:
item = ItcastItem()
# 返回的是一个selector对象列表, 用extract_first拿到第一个
item["name"] = div.xpath('./h3/text()').extract_first()
item["level"] = div.xpath('./h4/text()').extract_first()
item["description"] = div.xpath('./p/text()').extract_first()
yield item
编写管道
实现管道方法,
process_item
方法在
settings.py
中配置开启管道
class ItcastPipeline(object):
def process_item(self, item, spider):
print(dict(item))
return item
运行爬虫:
scrapy crawl 爬虫名
, 不显示别的, 在settings.py中添加配置:LOG_LEVEL = "WARNING"
scrapy中每个模块具体作用
入门使用
使用管道: 编写管道代码, 注册管道
使用回调函数处理不同的页面:
call_back=
, 如果不指定, 默认为parse存储不同数据时, 新建模型类, 管道里面使用
if isinstance(item,TencentItem)
判断不同的模型类存储相同数据时, 把item放到
meta
(字典, 用来传递数据)中, 通过response.meta
获取
import json
import scrapy
from tencent.items import TencentItem, DetailItem
class ZhaopinSpider(scrapy.Spider):
name = 'tencent_5'
allowed_domains = ['tencent.com']
base_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?pageIndex={}'
def start_requests(self):
# 引擎会自动回调这个方法, 提供给引擎首次请求的URL列表
for page in range(1, 489):
yield scrapy.Request(
url=self.base_url.format(page),
)
def parse(self, response):
"""
获取响应, 触发解析函数, 提取数据, 提取URL
:param response: 下载==>中央引擎==>爬虫 的response对象
:return: 数据 URL
"""
response_dict = json.loads(response.text)
response_data = response_dict["Data"]
info_count = response_data["Count"] # 所有显示的条数
for info in response_data["Posts"]:
item = TencentItem()
item["Name"] = info["RecruitPostName"]
item["Location"] = info["CountryName"] + info["LocationName"]
item["BG"] = info["BGName"]
item["Category"] = info["CategoryName"]
item["Time"] = info["LastUpdateTime"]
detail_info = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?postId='
post_id = info["PostId"]
yield scrapy.Request(
url=detail_info + post_id,
callback=self.parse_detail, # 设置回调解析函数
meta={"item": item} # 用meta携带item, response.meta, 字典类型
)
break # 测试
def parse_detail(self, response):
# 详情页解析函数
response_dict = json.loads(response.text)
item = response.meta["item"]
item["Responsibility"] = response_dict["Data"]["Responsibility"]
item["Requirement"] = response_dict["Data"]["Requirement"]
yield item
配置信息
# 项目名
BOT_NAME = 'tencent'
# 爬虫位置
SPIDER_MODULES = ['tencent.spiders']
NEWSPIDER_MODULE = 'tencent.spiders'
# 日志级别
LOG_LEVEL = "WARNING"
# 设置默认的用户身份 ==> 自己配置改成浏览器的
USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
# 是否遵循robots协议, 不让你怕, 是不是就不爬了 ==> 默认True
ROBOTSTXT_OBEY = False
# 最大连接数, 默认16, 并发请求数, 默认注释32
CONCURRENT_REQUESTS = 5
# 下载延迟
DOWNLOAD_DELAY = 3
# 对每个域名, 同时并发请求数
CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 对每个IP, 同时并发请求数
CONCURRENT_REQUESTS_PER_IP = 16
# 是否关闭cookie, 默认开启==>False
COOKIES_ENABLED = False
# 是否开启远程控制
TELNETCONSOLE_ENABLED = False
# 默认请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
# 设置爬虫中间件
SPIDER_MIDDLEWARES = {
'tencent.middlewares.TencentSpiderMiddleware': 543,
}
# 设置下载中间件
DOWNLOADER_MIDDLEWARES = {
'tencent.middlewares.TencentDownloaderMiddleware': 543,
}
# 设置扩展
EXTENSIONS = {
scrapy.extensions.telnet.TelnetConsole': None,
}
# 设置管道
ITEM_PIPELINES = {
'tencent.pipelines.TencentPipeline': 300,
'tencent.pipelines.MongodbListItemPipeline': 301,
'tencent.pipelines.MongodbDetailItemPipeline': 302,
}
# 设置用户名和密码
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = False
# 是否缓存请求
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache' # 本地 ==> 当前文件夹下.scrapy文件夹中的httpcache中, 下次对于相同域名, 先从缓存中提取请求
HTTPCACHE_IGNORE_HTTP_CODES = [403, 404, 500, ] # 哪些 请求状态 数不需要缓存的
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
CrawlSpider
自带去重功能
:
把提取的URL添加到
set
中, 如果提取的数据已经在set中了, 就不添加了
原因
: URL的查询字符串位置不同
, 对set()来说就是不同的数据
解决: 把参数也写到规则中
基本使用
必须继承自CrawlSpider
要设置
ruls
属性编写解析函数
爬取流程
通过规则提取链接
跟进链接获取内容
通过规则从内容中获取链接
跟进链接获取内容
提取规则
ruls
: 表示内容提取的规则, 包含规则列表(元组)
Rule
: 表示规则对象LinkrExtractor
: 链接提取器, 提取链接的规则参数allow=()
: 提取数据的条件(使用正则表达式)deny=()
: 过滤数据的条件(使用正则表达式), deny优先于allowallow_domains=()
: 允许的域名, 网站的静态文件和网页不在同一域名下deny_domains=()
: 过滤的域名, deny_domains 优先于 allow_domainsrestrict_xpaths=()
: 通过xpath提取链接tags=('a', 'area')
: 设置提取内容链接的标签, 默认是a, areaattrs=('href',)
: 设置提取内容标签的属性, 默认是hrefrestrict_css=()
: 通过CSS样式选择器提取内容strip=True
: 提取内容后去掉两边的空格
callback
: 当请求成功后的回调函数follow
:是否跟进,提取链接的内容这个循环是否继续
使用
//td[@class='12th']..
获取上一级元素,..
表示上一级获取第三个a标签的下一个a标签:
"//a[@id='3']/following-sibling::a[1]"
获取第三个a标签后面的第N个标签:
"//a[@id='3']/following-sibling::*[N]"
获取第三个a标签的上一个a标签:
"//a[@id='3']/preceding-sibling::a[1]"
获取第三个a标签的前面的第N个标签:
"//a[@id='3']/preceding-sibling::*[N]"
获取第三个a标签的父标签:
"//a[@id=='3']/.."
如果在for循环下面就用
extract()
, 如果直接取值就用extract_first()(列表取值)
评论区