- 状态:doing
1.框架介绍
安利这个博客:
爬虫框架Scrapy的讲解
对数据的处理流程如下:
2.实践
reference: 原创 Scrapy框架实战项目
写爬虫的时候,整体的流程如下:
2.1 建项目
scrapy startproject 一个项目名
2.2 创建spider文件
scrapy genspider [options]
用模板创建一个spider文件,记得要先进到工程的目录下❗️
例如,scrapy genspider example example.com
,其中的参数example是spider文件中的name,也是在运行这个爬虫时scrapy crawl
后跟的那个参数;example.com是spider文件中的start_urls,注意这里千万不要带http://❗️也不要带双引号❗️一般来说,example不能和第一步的项目名重名。
2.3 定义抓取字段
打开items.py文件,把你想要抓取的字段用scrapy.Field()这种形式定义出来,例如:
import scrapy
class TubatuItem(scrapy.Item):
# define the fields for your item here like:
content_url = scrapy.Field() # 请求的url地址
image_urls = scrapy.Field()
2.4 写spider.py
开始写spider文件中的爬虫步骤,spider文件的整体结构是这样的:
import scrapy
class TubatuTestSpider(scrapy.Spider):
name = "tubatu_test"
astart_urls = ['https://xiaoguotu.to8to.com/tuce//']
def parse(self, response):
pass
这几个参数或者方法的意思如下:
name = "" :这个爬虫唯一标志的名称;
allow_domains = []:规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略;
start_urls = ():第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。
parse(self, response) :解析的方法,每个初始URL完成下载后将被调用。作用是解析返回的网页数据(response.body)、提取结构化数据(生成item),以及生成需要下一页的URL请求。
我们在parse文件里写爬虫的操作,存在以下问题:
①例如对于一级url的页面来说,我们可能需要抓取页面上的文本信息和二级url链接,怎么对二级url链接中的页面内容再次进行抓取呢?这里,我们使用yield方法发起请求,并定义一个新的页面分析方法作为回调函数去处理二级url中的响应:
yield scrapy.Request(url=二级url,callback=self.basicInfo())
这是个Request对象,scrapy会先去请求这个二级url,然后去执行回调函数。(request加入爬取队列)
②一级url不止一个网页,如果需要翻页获取全部的信息怎么处理?
思路是:找到下一页的url,然后依然使用yield方法发请求,只不过这里传参的url换成下一页的url(我的理解:如果页面存在“下一页”按钮,把这个按钮带的href值传进来;还有一种情况是当前页面的url会有规律的进行变化,例如_1,_2这种就比较简单了),回调函数传入parse方法。(item类型则使用pipeline处理)
但是要记得,这个yield的执行需要放在当前页面全部处理完成之后❗️
③既然这个parse提取的是Item类的数据,那我们自己写对二级url处理的时候怎么搞呢?
在写scrapy项目的第3步,我们在items.py文件里定义的就是这种Item类,它类似于Python当中的词典,我们需要将解析得到的数据放到这类中,因此需要先实例化item,然后以字典的方式进行值的传递,例如:
tubatu_info = TubatuItem()
tubatu_info['content_url']='abcdefg.com'
进一步来说,如果我们的函数返回的是item类型的函数,需要使用yield方法将其送到pipeline中,决定它下一步是文件存储?还是数据库存储?等等。。
好啦,我们写完spider的内容了,接下来就是怎么存储的问题咯
2.5 写pipelines.py
由于我现在没有装数据库,打算先搞一下文件和图片存储的pipeline方式。
item pipiline组件是一个独立的Python类,其中每个item pipeline组件都需要调用该process_item(),这个方法返回的是Item对象。此方法有两个参数,一个是item,即要处理的Item对象,另一个参数是spider
原始是这个样子的,我们需要什么方式的pipeline就需要定制什么样的pipeline组件:
class SomethingPipeline(object):
def __init__(self):
# 可选实现,做参数初始化等
# doing something
def process_item(self, item, spider):
# item (Item 对象) – 被爬取的item
# spider (Spider 对象) – 爬取该item的spider
# 这个方法必须实现,每个item pipeline组件都需要调用该方法,
# 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
return item
def open_spider(self, spider):
# spider (Spider 对象) – 被开启的spider
# 可选实现,当spider被开启时,这个方法被调用。
def close_spider(self, spider):
# spider (Spider 对象) – 被关闭的spider
# 可选实现,当spider被关闭时,这个方法被调用
当然这上面的后两个方法我还没有试过,也不知道是啥意思。❓
定义文本存储的组件如下:
class TubatuPipeline(object):
def process_item(self, item, spider):
# print(item)
# print(type(item))
# print(len(item))
with open('tu8tu.txt','a',encoding='utf-8') as f:
f.write(str(item['content_id'])+' '+item['content_name']+' '+item['content_url']+' '+str(item['nick_name'])+' '+item['pic_name']+' '+item['pic_url']+'\n')
return item
定义图片存储的组件如下:(p.s这个是抄的,还没来得及研究,明天看)
class TubatuImagePipeline(ImagesPipeline):
# def get_media_requests(self, item, info):
# # 根据image_urls中指定的url进行爬取,该方法默认即可
# pass
def item_completed(self, results, item, info):
# 图片下载完成之后,处理结果的方法,返回的是一个二元组
# 返回的格式:(success, image_info_or_failure) 第一个元素表示图片是否下载成功;第二个元素是一个字典,包含了image的信息
image_paths = [x["path"] for ok, x in results if ok] # 通过列表生成式
if not image_paths:
raise DropItem("Item contains no images") # 抛出异常,图片下载失败(注意要导入DropItem模块)
return item
def file_path(self, request, response=None, info=None):
# 用于给下载的图片设置文件名称
url = request.url
file_name = url.split("/")[-1] # 通过 / 来进行分割,选取最后一个
# 如:"https://pic.to8to.com/case/2018/08/27/20180827165930-fac62168_284.jpg"
# 拆分后为:20180827165930-fac62168_284.jpg
return file_name
定义csv组件:
class TencentCSVPipeline(object):
def __init__(self):
store_file = os.path.dirname(__file__)+'/spiders/hireInfo.csv'
self.file = open(store_file,"a+",encoding='utf-8',newline='')
self.writer = csv.writer(self.file)
self.writer.writerow(['title', 'bu', 'place', 'type', 'time'])
def process_item(self, item, spider):
self.writer.writerow([item['hire_title'],item['hire_bu'],item['hire_place'],item['hire_type'],item['hire_time']])
return item
定义完组件之后,需要将它的类添加到 settings.py文件ITEM_PIPELINES 配置,例如:
ITEM_PIPELINES = {
# 'tubatu.pipelines.TubatuPipeline': 300,
'tubatu.pipelines.TubatuImagePipeline': 300,//数字越小,组件的优先级越高
}
2.6 反反爬虫(to be continued)——middlewares.py
下载中间件(Downloader Middlewares)的作用:
- 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
- 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
下载中间件需要去settings.py里激活。
自己定义一个,格式如下:
class RandomUserAgent(object):
def process_request(self, request, spider):
#要是这么写,需要在settings.py里定义一批USER_AGENTS。
useragent = random.choice(USER_AGENTS)
request.headers.setdefault("User-Agent", useragent)
最后需要设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类
2.7 爬虫项目的运行方式
如果觉得每次都需要开个命令行太麻烦,可以在当前目录下建一个main.py文件,写入下面代码:
from scrapy import cmdline
cmdline.execute("scrapy crawl 项目名称".split())
2.8 零碎知识
2.8.1 Request
参数如下:
url: 就是进行下一步处理的url;
callback: 请求返回的Response由哪个函数来处理;
method: 请求一般不需要指定,默认GET方法,可设置为"GET", "POST", "PUT"等,且保证字符串大写;
headers:不多说了;
meta: 比较常用,在不同的请求之间传递数据使用的,字典类型。在回调函数中使用时,用response.request.meta[]的形式进行取值。
encoding: 使用默认的 'utf-8' 就行;
dont_filter: 表明该请求不由调度器过滤。这是当你想使用多次执行相同的请求,忽略重复的过滤器。默认为False;
errback: 指定错误处理函数
2.8.2 scrapy.Spider类
参数如下:
__init__() : 初始化爬虫名字和start_urls列表;
其中包含的属性有:
- name:定义spider名字的字符串。
- allowed_domains:包含了spider允许爬取的域名(domain)的列表,可选。
- start_urls:初始URL元祖/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。
start_requests() :调用make_requests_from url生成Requests对象交给Scrapy下载并返回response;
- 该方法必须返回一个可迭代对象(iterable),一般是[]这样的形式
- 当spider启动爬取并且未指定start_urls时,该方法被调用。
parse(): 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。
- 当请求url返回网页没有指定回调函数时,默认的Request对象回调函数
2.8.3 LinkExtractors
常用于全站爬取时获取url,使用时一般结合Crawler和Rule,提取匹配,并使用spider的方法进行分析;并跟进链接。Rule里的callback 千万不能写 parse❗️
使用方法: python爬虫scrapy的LinkExtractor
2.8.4 模拟登陆,发送POST请求写法
import scrapy
class LoginSpider(scrapy.Spider):
name = 'example.com'
start_urls = ['http://www.example.com/users/login.php']
def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'john', 'password': 'secret'},
callback=self.after_login
)
def after_login(self, response):
# check login succeed before going on
if "authentication failed" in response.body:
self.log("Login failed", level=log.ERROR)
return
2.8.5 ItemLoader(还未看...❗️)
Scrapy爬虫入门教程七 Item Loaders(项目加载器)
3.实践demo
github:
https://github.com/mikiihuang/spider_learn
这几天计划写几个spider项目熟悉一下,都会同步更新在这个仓库里~欢迎大佬指教~
需要登陆的:
Scrapy模拟登陆豆瓣抓取数据
4.遇到的问题
Scrapy爬取下来的数据不全,为什么总会有遗漏
爬虫出现Forbidden by robots.txt
Scrapy框架爬取有验证码的登录网站