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请求是如何发送和分页的构造。

接下来我们对数据持久化。

posted @ 2020-07-06 22:38  Mn猿  阅读(324)  评论(0编辑  收藏  举报