4.scrapy爬虫文件
scrapy.Spider
这一节我们来聊一聊爬虫文件
1. 请求发送
# -*- coding: utf-8 -*-
import scrapy
class BaiduSpider(scrapy.Spider):
name = 'baidu'
allowed_domains = ['baidu.com']
start_urls = ['http://baidu.com/']
def parse(self, response):
print(response.text)
我们来一步一步分析这个文件中的代码是如何运行的
1.1 start_urls
这是一个列表, 列表的每一个元素都一个一个url, 当我们的爬虫启动的时候会循环这个列表, 然后会把url当做请求的地址发送出去, 但是在本文件的代码层面上是没有体现的, 这里我们点击源码去一探究竟.
# 点击scrapy.Spiderr源码中 当我们运行爬虫的时候 就会触发 start_requests 这个方法
def start_requests(self):
# scrapy 默认的起始函数(当执行启动命令时,会触发这个函数)
cls = self.__class__
if not self.start_urls and hasattr(self, 'start_url'):
raise AttributeError(
"Crawling could not start: 'start_urls' not found "
"or empty (but found 'start_url' attribute instead, "
"did you miss an 's'?)")
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
# 每一个url封装成Request对象,交给调度器 这里dont_filter=True 默认不过滤
yield Request(url, dont_filter=True)
这里我们重点是看else: 后面的代码, 就先不看前面的两个 if 了, 就是遍历然后把每一个url封装成Request对象,交给调度器, 然后发送请求, 默认是GET请求, 回调函数是parse
这里我们也可以自己重写发请求的方法, 以及自定义回调函数.
# -*- coding: utf-8 -*-
import scrapy
class BaiduSpider(scrapy.Spider):
name = 'baidu'
allowed_domains = ['baidu.com']
start_urls = ['http://baidu.com/']
def start_requests(self):
for url in self.start_urls:
# 每一个url封装成Request对象,交给调度器
yield Request(url, dont_filter=True, callback=self.my_parse)
def my_parse(self, response):
print(response.text)
1.2 Request对象
Request(url[, callback,method='GET', header,body, cookies, meta, encoding='utf-8', priority=0,dont_filte=False, errback])
下面介绍这些参数
- url (string) ---------------> 请求页面的url地址,bytes或者str类型。
- callback (callable) -------> 页面解析函数(回调函数) callable类型 Request对象请求的页面下载完成后,由该参数指定页面解析函数被调用。如果没有定义该参数,默认为parse方法。
- method (string) ---------> http请求的方法,默认为GET
- header (dict) -------------> http 请求的头部字典,dict类型,例如{“Accrpt”:"text/html","User-Agent":"Mozilla/5.0"},如果其中某一项的值为空,就表示不发送该项http头部,例如:{“cookie”:None} 表示禁止发生cookie.
- body (str) -------------------> http请求的正文,bytes或者str类型。
- cookies (dict or cookiejar对象) -----> cookies 信息字典,dict类型。
- meta (dict) ------------------> Request的元素数据字典,dict类型,用于框架中其他组件传递信息,比如中间件Item Pipeline. 其他组件可以使Request对象的meta属性访问该元素字典(request.meta),也用于给响应处理函数传递信息。
- encoding (string) ---------> url和body参数的编码方式,默认为utf-8,如果传入str类型的参数,就使用该参数对其进行编码。
- priority (int) ----------------> 请求的优先级默认为0 ,优先级高的请求先下载。
- dont_filter (boolean) ----> 指定该请求是否被 Scheduler过滤。该参数可以是request重复使用(Scheduler默认过滤重复请求)。但是默认的start_ulrs中的请求是dont_filter = True 不过滤。
- errback (callable) --------> 请求出现异常或者出现http错时的回调函数。
1.3 发送post请求
发送post请求的话,这里归纳三种
1.3.1 基于Request对象
这种是最接近底层的,通过自己改写请求方式和构造提交的数据
from scrapy import Request
headers = {'Content-Type':'application/x-www-form-urlencoded'}
data = {'k1':'v1','k2','v2'}
# 通过方法将data的键值对编码成下面这种格式
body = b'k1=v1&k2=v2'
Request(url, method='POST', headers=headers,body=body, callback=self.my_parse)
1.3.2 基于fromRequest对象
from scrapy import FromRequest
data = {} # 以键值对的形式存放要提交的数据 内部会把数据编码成k1=v1&k2=v2这种格式,发送出去
# 此时的请求头中是
# self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
# 这是浏览器原生的一种提交数据的方式 大部分服务端语言都对这种方式有很好的支持
FromRequest(url, callback=self.my_parse,data=data) # 默认不指定请求方式就是POST请求
1.3.3 基于JsonRuquest对象 ( 推荐 )
from scrapy.http import JsonRequest
data = {} # 以键值对的形式存放要提交的数据 内部会把当前data序列化成json格式,然后直接发送
# 此时的请求头中是self.headers.setdefault('Content-Type', 'application/json')
# 现在比较流行的一种方式(写代码,推荐这种)
FromRequest(url, callback=self.my_parse,data=data) # 默认不指定请求方式就是POST请求
2. 提一下cookies
有些网站想要爬取是需要登录的, 登录后服务端会返回一串cookies给客户端, 这样客户端再发请求, 就会带着cookie, 服务端就会把它认为是已登录的账号了
解析cookies
from scrapy.http.cookies import CookieJar
cookie_jar = CookieJar()
cookie_jar.extract_cookies(response,response.request)
# response.request 是产生该http响应的Request对象
print(cookie_jar) # 是一个cookie_jar对象
# 还有一种从响应头中获取服务端设置的cookie
cookies = response.headers.getlist('Set-Cookie')
print(cookies)
# [b'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/']
3. 回调函数
def parse(self, response):
# 默认的回调函数
print(response.text)
3.1 response对象
response对象是用来描述一个HTTP响应的,一般是和request成对出现,你用浏览器浏览网页的时候,给网站服务器一个request(请求),然后网站服务器根据你请求的内容给你一个response(响应)。
那 Scrapy中的response又是什么东西?
其实这个response和上边讲到的作用一样,不过在Scrapy中的response是一个基类,根据网站响应内容的不同,
response还有三个子类 :
- TextResponse
- HtmlResponse
- XmlResponse
当页面下载完成之后,Scrapy中的下载器会根据HTML响应头部中的ContentType信息创建相应的子类对象。
3.2 response常见属性
属性名 | 作用 |
---|---|
url | HTTP相应的 URL地址,str类型的 |
status | HTTP响应状态码,int类型的(在pycharm的控制台中你可以看到,例如200,404) |
body | HTTP响应正文,bytes类型 |
text | 文本形式的HTTP响应正文,str类型,由response.body使用response.encoding解码得到的 |
encoding | HTTP响应正文的编码(有时候会出现烦人的乱码问题,那你得注意是不是这个属性出问题了) |
request | 产生该HTTP响应的Requset对象 |
meta | response.request.meta 在构造request对象的时候,可以将要传递个响应处理函数的信息通过meta参数传入;响应处理函数处理响应时候,通过response.meta将信息取出 |
# text属性来源
response.text=response.body.decode(response.encoding)
3.3 response常用方法
3.3.1 xpath ( ) 语法
使用xpath选择器提取Response中的数据,实际上它是response.selector.xpath方法的快捷方式。
符号 | 名称 | 含义 |
---|---|---|
/ | 绝对路径 | 表示从根节点开始选取 |
// | 相对路径 | 表示选择从任意位置的某个节点, 而不考虑他们的位置 |
# 匹配所有的a标签 返回的是一个列表 每个元素都是一个selector对象可以继续.xpath
hxs = response.xpath('//a')
# 匹配第二个a标签
hxs = response.xpath('//a[2]')
# 匹配有id属性的所有a标签
hxs = response.xpath('//a[@id]')
# 匹配id属性等于i1的所有a标签
hxs = response.xpath('//a[@id="i1"]')
# 匹配href属性等于link.html并且id属性等于i1的所有a标签
hxs = response.xpath('//a[@href="link.html"][@id="i1"]')
# 匹配href属性中存在link的所有a标签
hxs = response.xpath('//a[contains(@href, "link")]')
# 匹配href属性中以link开头的所有a标签
hxs = response.xpath('//a[starts-with(@href, "link")]')
# 正则匹配 匹配id属性中满足i\d+正则表达式的所有a标签
hxs = response.xpath('//a[re:test(@id, "i\d+")]')
# 获取所有a标签的文本 返回的是一个列表 每个元素都是字符串
hxs = response.xpath('//a/text()').extract()
# 获取所有a标签的href属性值 返回的是一个列表 每个元素都是字符串
hxs = response.xpath('//a/@href').extract()
# 获取第一个a标签的href属性值 返回一个字符串
hxs = response.xpath('//a/@href').extract_first()
3.3.2 css ( ) 语法
使用css选择器提取Response中的数据,实际上它是response.selector.css方法的快捷方式。
使用css相对于xpath写法简单一些, 但是内部还是把css的查询语句转换成xpath的查询语句, 只是查询的接口用法变的简单了。所以你写xpath的话, 是不是相对于css要少一步转换
def css(self, query):
"""
Apply the given CSS selector and return a :class:`SelectorList` instance.
``query`` is a string containing the CSS selector to apply.
In the background, CSS queries are translated into XPath queries using
`cssselect`_ library and run ``.xpath()`` method.
.. _cssselect: https://pypi.python.org/pypi/cssselect/
"""
# 通过_css2xpath方法转换
return self.xpath(self._css2xpath(query))
表示式 | 描述 |
---|---|
a | 选中所有a标签 |
a,p | 选中所有a标签和p标签 |
div p | 选中div标签后代中所有的p标签 |
div>p | 选中div标签子代中的所有p标签 |
.title | 选中class属性是title的所有标签 |
#id1 | 选中id属性是id1的所有标签 |
[attr] | 选中包含attr属性的所有标签 |
[attr=value] | 选中attr属性等于value的所有标签 |
[ATTR~=VALUE] | 选中包含ATTR属性且值包含VALUE的元素 |
div::attr(class) | 选中所有div标签的class属性值 |
div::text | 选中所有的div标签的文本内容 |
# 返回一个列表,每一个元素都是一个selector对象,可以继续.css()
response.css()
# 返回匹配到第一个字符串
response.css().get()
# 返回一个列表,每一个元素都是一个字符串
response.css().getall()
当然你也可以用你熟悉的bs4这个解析库来对响应回来的网页内容进行提取。
3.3.3 urljoin(url)
用于构造绝对的url。当传入的url参数是一个相对的url时,根据response.urljoin(url),即可获得绝对路径。
urljoin(url) 用于构造绝对url ,当传入的url参数是一个相对地址的时候,这个伙计会根据response.url计算出相
应的绝对地址。
举个栗子:
response.url=‘https://mp.csdn.net’,url=‘mdeditor/85640067’。
则response.joinurl(url)的值为‘https://mp.csdn.net/mdeditor/85640067’
然后就可以根据这个构造出来的新的url,重新构造request,然后爬取下一页面的内容了
4. 翻页
既然是用框架爬虫那么会爬很多的网页,这就会涉及到翻页的操作了
4.1 基于start_urls
你可以把你想要爬取的所有url全部放在start_urls中, 因为一般翻页url都是有一定的固定格式的,可以通过列表的
推导式生成。
# 这里的网址只是为了举例
start_urls = ['http://www.xiaohua.com/index?page={}'.formart(i) for i in range(1,101)]
# 还记得上面提到的,爬虫文件运行后会对start_url进行遍历, 会对每个url发请求请求
4.2 通过meta传参
meta(字典)是Request对象中的一个参数, 可以在请求和响应之间传递
Request(url=url,meta={'page':'1'})
def paser(self,response)
page = response.meta.get('page') # 获取上次请求中的meta中的page对应的值
new_page = page += 1
url = 'http://www.xiaohua.com/index?page={}'.formate(new_page)
....
yield Request(url=url,meta={'page':new_page})
5. 总结
无论是哪种选择器,只要你能够熟练使用,提取到你想要的内容就可以了,但是我觉得xpath还是比较全的。
以及要掌握post请求是如何发送和分页的构造。
接下来我们对数据持久化。