• 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属性中满足id+正则表达式的所有a标签
    hxs = response.xpath('//a[re:test(@id, "id+")]')
    
    # 获取所有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请求是如何发送和分页的构造。

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

  • 相关阅读:
    Linux任务前后台的切换
    如何给html元素的onclick事件传递参数即如何获取html标签的data
    关键词多空格处理
    tp3常量
    php 正则判断是否是手机号码
    thinkphp 初始化
    删除图标
    time() 在thinkphp 3.2.3 模板格式化输出
    iOS工程如何支持64-bit
    调试instruments
  • 原文地址:https://www.cnblogs.com/xcymn/p/13258075.html
Copyright © 2020-2023  润新知