最近所学——爬虫心得以及学习体会(本人的第一篇博客)

  由于论文的关系,要大量的微博文本数据,在网上查了很多,没有可以直接用的现成数据,因此就入了爬虫的坑,通过同学介绍看了《精通Python网络爬虫》的书,也结合一些大牛的博客,如愿获得了自己想要的数据。在这主要记录一下自己学习这本书的心得、自己爬取微博数据的过程以及中途遇到的一些问题。

  关于精通Python网络爬虫这本书,对于一个从来没解除过爬虫的我来说,这可以算是一个非常好入门的书籍了,只要稍微有点Python的基础知识就可以很好学习这本书了。通过学习这本书,发现其实对于爬取web数据来说,最重要的就是如何获取大量的数据以及对于想要的数据的提取了,这里就不得不说到对于网页地址的分析以及正则表达式了,以及如何模拟登陆还有关于爬虫的Scrapy框架的知识。这本书的一些基础内容就不说了(包括爬虫的原理以及Python应用于爬虫的比如Urllib这样的库等等),直接进入我想要说的一些爬虫的重点,这些不算是难点,爬虫整体来说还是比较简单的。

  首先,说一下我对正则表达式的理解,通过python中的一些函数,可以将网页的源代码获取下来,但是如何从这些源代码中得到自己想要的数据呢(比如图片或者是一些文章标题以及微博内容),这就用到了正则表达式了,可能对于很多人来说正则表达式并不是个问题,因为正则表达式只要稍微用几次就可以很好的掌握了,但是对于刚开始的我,对于正则表达式确实是有点烦的,起初就是觉得有点麻烦,也不知道怎么写出正好可以匹配的正则来提取出想要的内容,通过学习之后,发现这是一个很灵活的东西,只要可以找到唯一识别的标识或字符串,就可以很容易的找到自己想要的东西,而且正则的编写也没有一个固定的格式,只要能匹配到自己想要的东西就是可以的。关于正则表达式的基础知识,我主要想说几个用的比较多也比较好用的:(1)“.”是用来匹配除换行符以外的任意字符;(2)“*”是用来匹配0次、1次或多次前面的原子;(3)“?”是用来匹配0次或1次前面的原子;(4)第4个就要说前面三个的组合了,也就是“.*?”了,这个就是懒惰模式了,不加问好就是贪婪模式,它们两个的区别是,懒惰模式采用的是就近匹配原则,可以让匹配结果更加的精确,二贪婪模式就是尽可能多地匹配。关于正则,我就不举例子了,只要能唯一匹配到自己想要的字符串,那就是好的正则表达式,不过要注意的是,正则表达式中有时候会加入“\”转义字符;还有一个要注意的是,在正则表达式中(),两个圆括号就代表了最终匹配的结果是圆括号里的内容,而不是整个正则表达式所匹配的内容,除非两个圆括号前面都有转义字符。除了正则表达式,还有一些获取网页源代码中自己想要的标签的方法,就是xpath表达式等。目前我也只会使用正则和xpath。

  接下来,说一下网页地址的分析,对于网上大量的数据,比如微博或者京东上的商品图片等等,他们都不是一下子囤积到一个页面上的,可以通过翻页操作查看到更多的内容,对于爬虫来说呢,为了高效简便的获取数据,就要使得爬虫可以大量自动的爬取想要的内容,这其中就设计到对于网页地址的分析了, 因为对于网页上的翻页操作,其实都是可以通过网址上的参数来反映出来的,首先打开想要获取的数据的首页https://list.jd.com/list.html?cat=9987,653,655,比如京东上的手机分类,为了自动获取大量的手机图片或数据,就要对页面进行分析,这时候点击网页最下面的“下一页”,这时观察网页地址为

https://list.jd.com/list.html?cat=9987,653,655&page=2&sort=sort_rank_asc&trans=1&JL=6_0_0&ms=6#J_main,可以发现和首页的网址相比,多了很多参数page=2&sort=sort_rank_asc&trans=1&JL=6_0_0&ms=6#J_main,对于多的这些参数,主要的是其中的page=2,这个就是控制网页翻页的参数page,不过对于不同的网站,控制翻页的参数不是都是page,有的可能是id,有的可能是pagenum等等。通过这样类似的分析就可以控制让爬虫自动按照顺序去爬取想要的数据。不过有的网站的页面分析并不像没这么简单的分析就可以得到翻页的控制参数,需要运用到一些抓包分析软件或者利用浏览器自带的工具(F12)等,这里我解除到了Fiddler,这是一个抓包分析软件,它可以详细地对HTTP请求进行分析,还能模拟对应的HTTP请求。通过学习这个软件,再难的网页翻页的控制参数都可以得到,这里我主要说一下关于微博数据的翻页,首先我铺垫一下,因为我是要获取微博“发现”下的热门分类的内容,对于一个分类,我要分析它的页面构成,微博页面不同于上面所说的京东商城,它的微博内容起初会显示15条,然后往下拉,后出现“正在加载”的字眼,根据鼠标往下拖动页面会自己加载,其实这个加载的过程就好比点击了“下一页”,由于微博内容全都显示在同一网页上,因此网址不会发生改变,这些网址的变化,只能通过抓包软件或者浏览器自带的工具来查看,通过Fiddler,我发现了其中的不同,对于起初的15条内容,他的真实数据网址是

http://d.weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=102803_ctg1_1388_-_ctg1_1388&from=faxian_hot&mod=fenlei&pagebar=0&tab=home¤t_page=1&pre_page=1&page=1&pl_name=Pl_Core_NewMixFeed__3&id=102803_ctg1_1388_-_ctg1_1388&script_uri=/102803_ctg1_1388_-_ctg1_1388&feed_type=1&domain_op=102803_ctg1_1388_-_ctg1_1388&__rnd=1504661045801 HTTP/1.1,

而加载一次的网址是

http://d.weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=102803_ctg1_1388_-_ctg1_1388&from=faxian_hot&mod=fenlei&pagebar=1&tab=home¤t_page=2&pre_page=1&page=1&pl_name=Pl_Core_NewMixFeed__3&id=102803_ctg1_1388_-_ctg1_1388&script_uri=/102803_ctg1_1388_-_ctg1_1388&feed_type=1&domain_op=102803_ctg1_1388_-_ctg1_1388&__rnd=1504661183573 HTTP/1.1

其中的102803_ctg1_1388_-_ctg1_1388代表的是一个类别,这里代表的是体育类,因此这个不重要,对于不同的类,只要将102803_ctg1_1388_-_ctg1_1388里后面两个数字替换一下即可。观察这两个网址,发现里面最主要的是四个参数,也就是pagebar、current_page、pre_page、page,其他的对页面都不会有什么影响,就不用管它们。对于这四个参数,再观察可以发现只有pagebar和current_page发生了变化,我刚开始分析,只加载了三四页,就得出结论微博数据的页面翻页由pagebar和current_page来控制的,可是当我爬取数据的时候,随着pagebar和current_page越来越大,发现页面获取的内容就是空的,说明是页面翻页的分析是不对的。之后我又通过Fiddler分析了24次加载的过程,发现在微博页面里,六页一个周期,没加载够六次,需要手动点击显示更多来显示更多的微博内容,分析了24次的网页地址结果我就不放上来了,有兴趣的可以通过抓包软件自己去看看,我直接把分析的结果放上来。加载24页后,我发现了页面的翻页是这四个参数来控制,而且还是有规律的,current_page是从1开始,没加载一页,就加1;而pagebar是从0开始,加载到4,0-4也就是5个页面,第6个页面pagebar就不在网页地址中了,然后下一次加载pagebar就继续从0开始,是一个循环,6是周期;然后就是pre_page,这个我理解的是当前大页码的意思,它的值,起初的六个页面pre_page都是1,第二次的六个页面它的值都是2,因此我之前分析了三四次的加载都看不出它的变化也是这个原因;page和pre_page类似,对于起初的六个页面中的前五个页面page是1,第六个页面page变成了2,杜宇第二次的六个页面中的前五个页面page是2,第六个页面就变成了3;通过这些分析,我把微博某一类别微博数据的页面翻页理解为由这四个参数来控制,而且还是一个二层页面的样子,由pre_page和page来控制大页面页码,由pagebar来控制小页页码,current_page来控制小页的数量,用这个分析继续去爬取数据,发现随着值的增大,获取的数据又编程了空,发现又分析错了。这次我又将页面的加载次数增加到30次,这时候四个参数的变化都和之前的分析一样,只是加载到第30页之后,在网站里就没有“显示更多”的字眼了,这时候不能加载更多的微博数据了,这时就有点不知道该怎么继续去分析页面翻页的控制参数了,然后我就打开了此时网页的源代码来查看,发现了,微博显示的这一大栏里,源代码里有个pagenum=1的字眼,这时我推断这个参数可能是控制参数,这样子的话,微博的页面翻页就是一个三层翻页过程了,有pagenum控制最大的页面,这个页面又分为5个中页,30个小页,也就是current_page从1加载到30,然后pagenum就加1了。这次利用这个分析构造出来的微博数据网页地址来获取微博数据,发现不会出现空页面了,因此这个分析就是正确的了。说了这么多关于页面翻页分析的东西,其实这个页面分析并没有那么难,只是是一个需要自己去细细观察和要有耐心的工作,针对不同的网站,只要分析出网页地址翻页或者关键词的控制参数,就可以自动的爬取数据了。对于关键词的参数控制,也是可以显示在网页地址中的,就比如刚才102803_ctg1_1388_-_ctg1_1388代表体育,102803_ctg1_2088_-_ctg1_2088就代表科技了,只要通过观察网页地址的变化以及一些经验就可以轻松的得出了。

  说完正则和网页地址的分析之后,我要说一下模拟登陆的东西了,这里主要以微博为例,因为对于微博这样的网站,它的微博浏览需要人们登陆了微博之后了,才可以浏览更多更多的微博,因为需要取模拟微博的登录,如果不模拟登陆,就不能获取得到自己想要的数据页面。模拟登陆的过程主要就是找到请求网站要发送的数据(即postdata),在给网站发送访问请求时,带上需要传递的postdata即可模拟网站的登录,对于微博的模拟登陆是比较难的,这里主要推荐http://blog.csdn.net/together_cz/article/details/70198369这个文章,大家想要获取微博数据的可以去看一下,对于获取微博数据,要结合抓包分析软件如Fiddler来进行分析,分析得到它需要传递的postdata,通过它也可以和之前一样分析出微博数据的真实地址来(微博数据的真实地址应该都是通过js渲染的,目前这个我也不太懂,但是获取数据只要能找到真实地址即可)。不过对于模拟登录,还有一些更简便的方法,那就是应用selenium来实现自动登录,应用selenium下的webdriver可以轻松加载一个浏览器引擎,通过向网站真实登录地址传递用户名和密码,还可以传递“点击登录”的操作,这样就可以完成网站(如微博)的模拟登陆了,代码贴到下面供大家参考。

from selenium import webdriver

browser =webdriver.PhantomJS(executable_path="D:/phantomjs-2.1.1-windows/bin/phantomjs.exe")  #加载某个浏览器引擎
  

browser.get("http://login.sina.com.cn/sso/login.phpclient=ssologin.js(v1.4.19)")   # 请求微博真实登录地址
time.sleep(3)
        browser.find_element_by_xpath('//input[@name="username"]').send_keys("你的用户名")
        browser.find_element_by_xpath('//input[@name="password"]').send_keys("你的密码")
        browser.find_element_by_xpath('//input[@class="W_btn_a btn_34px"]').click()
time.sleep(3)

其中的find_element_by_xpath方法是通过xpath表达式来找到真实登录网页源代码中输入用户名和密码以及点击登录的标签的。通过这简单的几行代码就可以完成微博的模拟登陆了。

  最后就是要说下scrapy框架了,scrapy框架是一个很简便的爬虫框架,里面可以很方便的设置一些避免爬虫被禁止的操作,比如禁止cookie,使用ip池用户代理池等,这就是settings.py。还可以对自己想要获取的数据设置一个存储容器,这就是scrapy框架的items.py。还可以对获取的数据进行后续的处理,比如存入数据库之类的,这就是pipelines.py。还有一个中间件文件middlewares.py,这个文件和scrapy中最重要的爬虫文件是最重要的两个文件,对于中间件文件中,我们可以设置一些类,然后在settings文件中启动这个类,这样,对于爬虫文件中每次发送的Request都会经过中间件,这样可以利用中间件去做一些操作(具体视情况而定,这个我也不是很懂,只是按照自己的理解来说的,还是需要积累更多经验才能彻底了解),而对于scrapy中的爬虫文件,这是scrapy中最重要的文件了,我在这里主要说下爬虫的运行流程,直接贴图片来展示,scrapy中的爬虫文件主要是继承了scrapy.Spider类的,直接放上Spider类的源码

class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this
    class.
    """

    name = None
    custom_settings = None

    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not getattr(self, 'name', None):
            raise ValueError("%s must have a name" % type(self).__name__)
        self.__dict__.update(kwargs)
        if not hasattr(self, 'start_urls'):
            self.start_urls = []

    @property
    def logger(self):
        logger = logging.getLogger(self.name)
        return logging.LoggerAdapter(logger, {'spider': self})

    def log(self, message, level=logging.DEBUG, **kw):
        """Log the given message at the given log level

        This helper wraps a log call to the logger within the spider, but you
        can use it directly (e.g. Spider.logger.info('msg')) or use any other
        Python logger too.
        """
        self.logger.log(level, message, **kw)

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = cls(*args, **kwargs)
        spider._set_crawler(crawler)
        return spider

    def set_crawler(self, crawler):
        warnings.warn("set_crawler is deprecated, instantiate and bound the "
                      "spider to this crawler with from_crawler method "
                      "instead.",
                      category=ScrapyDeprecationWarning, stacklevel=2)
        assert not hasattr(self, 'crawler'), "Spider already bounded to a " \
                                             "crawler"
        self._set_crawler(crawler)

    def _set_crawler(self, crawler):
        self.crawler = crawler
        self.settings = crawler.settings
        crawler.signals.connect(self.close, signals.spider_closed)

    def start_requests(self):
        for url in self.start_urls:
            yield self.make_requests_from_url(url)

    def make_requests_from_url(self, url):
        return Request(url, dont_filter=True)

    def parse(self, response):
        raise NotImplementedError

    @classmethod
    def update_settings(cls, settings):
        settings.setdict(cls.custom_settings or {}, priority='spider')

    @classmethod
    def handles_request(cls, request):
        return url_is_from_spider(request.url, cls)

    @staticmethod
    def close(spider, reason):
        closed = getattr(spider, 'closed', None)
        if callable(closed):
            return closed(reason)

    def __str__(self):
        return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))

    __repr__ = __str__

爬虫文件里的类,都会继承这个类,这个类就是提供最基本的功能。对于其中的参数和方法,具体理解如下(是截取别人所说的):

name:

  这个属性是字符串变量,是这个类的名称,代码会通过它来定位spider,所以它必须唯一,它是spider最重要的属性。回头看看源码中__init__的定义,可以发现这个属性是可以修改的,如果不喜欢或者有需要重命名spider的name,可以在启动的时候传参修改name属性。

allowed_domains:

  这个属性是一个列表,里面记载了允许采集的网站的域名,该值如果没定义或者为空时表示所有的域名都不进行过滤操作。如果url的域名不在这个变量中,那么这个url将不会被处理。不想使用域名过滤功能时可以在settings中注释掉OffsiteMiddleware, 个人不建议这么做。

start_urls:

  这个属性是一个列表或者元组,其作用是存放起始urls,相当于这次任务的种子。使用默认模板创建spider时,该值是个元组,创建元组并且只有一个元素时需要在元素后面添加“,”来消除歧义,不然会报错:“ValueError: Missing scheme in request url: h”。这边经常有人出错,为了避免这个错误可以根据上一章内容,将模板中start_urls的值设置为列表。

custom_settings:

  这个属性值是一个字典,存放settings键值对,用于覆盖项目中的settings.py的值,可以做到在一个项目中的不同spider可以有不同的配置。不过这个值要慎用,有些settings的值覆盖也没有起作用,eg:“LOG_FILE”。如果想每个spider都有自己的log文件的话就不能这么做。因为日志操作在这个方法执行之前,那么无论怎么改都改不了之前的行为。不过这个问题scrapy研发团队已经注意到了,相信不久的将来会进行处理的。

crawler:

  这个值从源码可以看出来自于方法from_crawler()。该值是一个Crawler 实例, 其作用后面的教程会讲解,这边就不细说了。

 settings:

  这个值也是来自于方法from_crawler()。是一个Settings 实例,这个后面也会细说,稍安勿躁哈。

logger:

  顾名思义,记录日志用的,也是后面讲,耐心等候哈。

from_crawler:

  这是一个类方法,scrapy创建spider的时候会调用。调用位置在crawler.py 的类Crawler中,源码可以自己去看看,就不带大家看了。这个方法的源码在上面,我们可以看到,在实例化这个spider以后,这个实例才有的settings和crawler属性,所以在__init__方法中是没法访问这俩属性的。如果非要在__init__方法中使用相关属性,那么只能重写该方法,大家可以尝试写写。

start_requests():

  这个方法必须返回一个可迭代对象,切记!!!!上面就有源码很简单,就不细说了。如果想对属性start_urls做一些操作(增删改),并希望结果作为种子url去采集网站的时候,可以重写这个方法来实现。有了这个方法,甚至都不用在代码中定义start_urls。比如我们想要读取持久化的url执行采集操作,那么就没必要转存进start_urls里面,可以直接请求这些urls。当种子urls需要post请求的话,也需要重写该方法。

 make_requests_from_url(url):

  这个方法顾名思义,要是还不懂就看看上面的源码。这里只说一点,因为这里的Request初始化没有回调方法,就是默认采用parse方法作为回调。另外这里的dont_filter值为True,这个值的作用是该url不会被过滤,至于具体细节请听下回分解。

 parse(self, response):

  这个方法对于有经验的同学来说再熟悉不过了,我就简单的说说。这个方法作为默认回调方法,Request没有指定回调方法的时候会调用它,这个回调方法和别的回调方法一样返回值只能是Request, 字典和item对象,或者它们的可迭代对象。

 log(message[, levelcomponent]):

  这个方法是对logger的包装,看看源码就好,没什么什么可说的。

closed(reason):

  当这个spider结束时这个方法会被调用,参数是一个字符串,是结束的原因。这种用法以后会介绍,这里只需记住,想在spider结束时做一些操作时可以写在这里。

  对于初学者,其中最主要需要查看的start_requests和make_requests_from_url方法了,这两个方法可以告诉你scrapy框架中爬虫运行起来时Request和Response是怎样被传递的。对于我现在的理解来说,当启动scrpay爬虫后,首先会根据设置来初始化,然后根据爬虫文件中的start_requests和make_requests_from_url来发送Request,然后Request也会经过中间件处理,最后返回的Response由爬虫文件中的parse接收,然后对Response进行处理,获取相应数据。因此可以通过中间件,以及修改start_requests和make_requests_from_url、parse来达到自己想要的要求(或逻辑)。

 

  最后,说下我最近学习爬虫的心得吧,在获取页面源码到编写正则提取数据的过程或者是模拟登陆的过程中,都要通过适当的输出来调式程序,看哪里运行了哪里没有运行,都可以通过适当的输出来检查。然后对于过程中碰到的任何问题,都要很有耐心的去解决,通过查阅浏览器或者和他人讨论,来解决问题。写博客只是为了记录下最近的学习过程,其中有哪里说得不对的,希望看到这篇博客的人见谅,也希望看到这篇博客的大牛们给我分享点经验和意见。这也是本人的第一篇博客,可能格式上有哪里看的不舒服的,希望可以见谅。希望这篇博客可以帮到你。

 

 

 
  

转载于:https://www.cnblogs.com/kkkwoniu/p/7496239.html

你可能感兴趣的:(最近所学——爬虫心得以及学习体会(本人的第一篇博客))