葡京赌场网址R语言从小木虫网页批量取考研调剂信息

同、从URL读博并返html树

Designer News.png

    1.1
Rcurl包

前段时间在design+code购进了一个上学iOS设计以及编码在线课程,使用Sketch设计App,然后采用Swift语言实现Designer
News客户端。作者Meng
To曾上马源及Github:MengTo/DesignerNewsApp ·
GitHub。虽然实现普Designer
News客户端基本功能,但是采取臃肿MVC(Model-View-Controller)架构,不易为代码的测试与复用,于是使ReactiveCocoa兑现MVVM(Model-View-View
Model)架构,加上一个用Objective-C实现之BDD测试框架Kiwi来单元测试,就好表现使开发iOS
App。

       运Rcurl包得方便之为服务器发出请求,捕获URI,get 和
post 表单。比R socktet连接而供再强水准的互相,并且支持
FTP/FTPS/TFTP,SSL/HTTPS,telnet
和cookies等。本文用到的函数是basicTextGatherer和getURL。想详细摸底之保险之可以点击参考资料的链接。

ReactiveCocoa

ReactiveCocoa是一个用Objective-C编写,具有函数式和响应式特性的编程框架。大多数之开发者他们排忧解难问题的思维方式都是怎么样好任务,通常的做法尽管是编制很多命令,然后修改主要数据结构的状态,这种编程范式叫做命令式编程(Imperative
Programming)。与命令式编程不同之凡函数式编程(Functional
Programming),思考问题的法是到位什么任务,怎样描述是职责。关于对函数式编程入门概念的知道,可以参照酷壳《函数式编程》这首文章,深入浅出对函数式编程的琢磨方式、特性和技能通过一些示范来讲学。

        R命令:

ReactiveCocoa解决哪些问题?

  • 目标中状态及状态的倚重过多问题
    借用ReactiveCocoa中一个例子来说明:用户在登录界面时,有一个用户称输入框和密码输入框,还有一个记名按钮。登录交互要求如下:

  • 当用户称以及密码可验证格式,并且之前还不曾登录时,登录按钮才会点击。

  • 当点击登录成功登录后,设置已报到状态。

俗的做法代码如下:

static void *ObservationContext = &ObservationContext;

- (void)viewDidLoad {
   [super viewDidLoad];

   [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext];
   [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager];

   [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
   [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
   [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)dealloc {
   [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext];
   [NSNotificationCenter.defaultCenter removeObserver:self];
}

- (void)updateLogInButton {
   BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0;
   BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn;
   self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn;
}

- (IBAction)logInPressed:(UIButton *)sender {
   [[LoginManager sharedManager]
       logInWithUsername:self.usernameTextField.text
       password:self.passwordTextField.text
       success:^{
           self.loggedIn = YES;
       } failure:^(NSError *error) {
           [self presentError:error];
       }];
}

- (void)loggedOut:(NSNotification *)notification {
   self.loggedIn = NO;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
   if (context == ObservationContext) {
       [self updateLogInButton];
   } else {
       [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
   }
}

如上使用KVO、Notification、Target-Action等处理事件或信息之不二法门编写的代码分散到各个地方,变得乱七八糟和不便掌握;但是利用RACSignal统一处理吧,代码更加简明与易读。使用RAC后代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    @weakify(self);

    RAC(self.logInButton, enabled) = [RACSignal
        combineLatest:@[
            self.usernameTextField.rac_textSignal,
            self.passwordTextField.rac_textSignal,
            RACObserve(LoginManager.sharedManager, loggingIn),
            RACObserve(self, loggedIn)
        ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) {
            return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue);
        }];

    [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) {
        @strongify(self);

        RACSignal *loginSignal = [LoginManager.sharedManager
            logInWithUsername:self.usernameTextField.text
            password:self.passwordTextField.text];

            [loginSignal subscribeError:^(NSError *error) {
                @strongify(self);
                [self presentError:error];
            } completed:^{
                @strongify(self);
                self.loggedIn = YES;
            }];
    }];

    RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter
        rac_addObserverForName:UserDidLogOutNotification object:nil]
        mapReplace:@NO];
}
  • 俗MVC架构中,由于Controller承担数据印证、映射数据模型到View和操作View层次结构等多独事,导致Controller过于臃肿,不便利代码的复用和测试。
    以风俗的MVC架构中,主要有Model,
    View和Controller三部分组成。Model主要是保留数据及拍卖事务逻辑,View将数据展示,而Controller调解关于Model和View之间的持有交互。
    当数码到时,Model通过Key-Value Observation来打招呼View Controller,
    然后View Controller更新View。当View与用户交互后,View
    Controller更新Model。

Typical MVC paradigm.png

刚刚而您所显现,View
Controller隐式承担多责:数据印证、映射数据模型到View和操作View层次结构。MVVM将多逻辑从View
Controller移走及View-Model,等引见完ReactiveCocoa后会见介绍MVVM架构。还有部分关于什么减负View
Controller好文章请参见objc中国更轻量的View
Controllers系列:

  • 复轻量的 View
    Controllers

  • 整洁的 Table View
    代码

  • 测试 View
    Controllers

  • 采用Signal来替代KVO、Notification、Delegate和Target-Action等传递信息
    iOS开发被生出多消息传递方式,KVO、Notification、Delegate、Block和Target-Action,对于它中间产生什么差别与如何抉择要参见《消息传递机制》。但RAC提供RACSignal来归并消息传递机制,不再为哪挑选何种传递信息方式而烦恼。

    RAC对常用UI控件事件开展封装成一个RACSignal对象,以便对有的各种风波开展监听。
    KVO示例代码如下:

// When self.username changes, logs the new name to the console.
//
// RACObserve(self, username) creates a new RACSignal that sends the current
// value of self.username, then the new value whenever it changes.
// -subscribeNext: will execute the block whenever the signal sends a value.
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
}];

Target-Action示例代码如下:

// Logs a message whenever the button is pressed.
//
// RACCommand creates signals to represent UI actions. Each signal can
// represent a button press, for example, and have additional work associated
// with it.
//
// -rac_command is an addition to NSButton. The button will send itself on that
// command whenever it's pressed.
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
    NSLog(@"button was pressed!");
    return [RACSignal empty];
}];

Notification示例代码如下:

 // Respond to when email text start and end editing
 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
      [self.emailImageView animate];
      self.emailImageView.image = [UIImage imageNamed:@"icon-mail-active"];
      self.emailTextField.background = [UIImage imageNamed:@"input-outline-active"];
  }];

 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
      self.emailTextField.background = [UIImage imageNamed:@"input-outline"];
      self.emailImageView.image = [UIImage imageNamed:@"icon-mail"];
  }];

除此之外,还可以采取AFNetworking做客服务器后针对回数据由创始一个RACSignal。示例代码如下:

 + (RACSubject*)storiesForSection:(NSString*)section page:(NSInteger)page
{
    RACSubject* signal = [RACSubject subject];

    NSDictionary* parameters = @{
        @"page" : [NSString stringWithFormat:@"%ld", (long)page],
        @"client_id" : clientID
    };

    [[AFHTTPSessionManager manager] GET:[DesignerNewsURL stroiesURLString] parameters:parameters success:^(NSURLSessionDataTask* task, id responseObject) {
                NSLog(@"url string = %@", task.currentRequest.URL);
                [signal sendNext:responseObject];
                [signal sendCompleted];
    } failure:^(NSURLSessionDataTask* task, NSError* error) {
                NSLog(@"url string = %@", task.currentRequest.URL);
                [signal sendError:error];
    }];

    return signal;
}

微朋友可以感到有点奇怪,上面代码明明返回的是RACSubject,而非是RACSignal,其实RACSubject是RACSignal的子类,但是RACSubject写起代码更加从简,所以用RACSubject(官方不推荐下)。等下用RAC核心类设计时,你尽管会见了解她中的涉及以及怎么挑选。

        h <- basicTextGatherer( )  
# 查看服务器返回的峰信息
        txt <- getURL(url,
headerfunction = h$update,.encoding=”UTF-8…”)  # 返回字符串形式html

ReactiveCocoa核心类设计

关于RAC核心类设计,官方文档有详细的说明:Framework
Overview

      
参数url即为需要拜访的url这里参数用headerfunction用到上一致长长的命令归来的峰信息,.encoding指定网页的编码方式为“UTF-8″。

Sequence和Signal基本操作

了解完个RAC核心类设计下,要学会对Sequence和Signal基本操作,比如:用signal执行side
effects,转换streams, 合并stream和统一signal。详情请查阅官方文档:Basic
Operators

      
网页的编码方式有诸多,一般采用UTF-8,一些中文网页编码方式为“gbk”,可以于浏览器的网页代码查看或getURL返回的字符串看到。

MVVM架构

MVVM high level.png

于MVVM架构中,通常还将view和view
controller看做一个完好。相对于事先MVC架构中view
controller执行很多当view和model之间数据映射和交互的办事,现在以她交给view
model去举行。
有关选择哪种体制来更新view
model或view是无强制的,但常见咱们且选择ReactiveCocoa。ReactiveCocoa会监听model的更改然后用这些改动映射到view
model的性被,并且可推行有业务逻辑。
选举个例来说,有一个model包含一个dateAdded的性质,我思念监听它的成形然后更新view
model的dateAdded属性。但model的dateAdded属性的数据类型是NSDate,而view
model的数据类型是NSString,所以在view
model的init方法中展开数量绑定,但需多少类型转换。示例代码如下:

RAC(self,dateAdded) = [RACObserve(self.model,dateAdded) map:^(NSDate*date){ 
    return [[ViewModel dateFormatter] stringFromDate:date];
}];

ViewModel调用dateFormatter进行多少易,且方法dateFormatter可以复用到其它地方。然后view
controller监听view model的dateAdded属性且绑定到label的text属性。

RAC(self.label,text) = RACObserve(self.viewModel,dateAdded);

今咱们抽象出日期变到字符串的逻辑到view
model,使得代码可以测试复用,并且帮view controller瘦身

       小木虫网页代码查看

Kiwi

Kiwi是一个iOS行为让开发(Behavior Driven
Development)的库房。相比于Xcode提供单元测试的XCTest是起测试的角度思考问题,而Kiwi是自行为的角度思考问题,测试用例都随三段式Given-When-Then的描述,清晰地表达测试用例是测试什么样的目标要数据结构,在根据什么上下文或现象,然后做出什么响应。

describe(@"Team", ^{
    context(@"when newly created", ^{
        it(@"has a name", ^{
            id team = [Team team];
            [[team.name should] equal:@"Black Hawks"];
        });

        it(@"has 11 players", ^{
            id team = [Team team];
            [[[team should] have:11] players];
        });
    });
});

咱俩充分爱因上下文将那个提为Given..When..Then的三段式自然语言

Given a Team, when be newly created, it should have a name, it should have 11 player

就此Xcode自带的XCTest测试框架写过测试代码的情侣或者体会至,以上代码更加便于阅读与掌握。就算以后来新的开发者入或者修护代码时,不欲极度可怜的基金去读与了解代码。具体怎么着下Kiwi,请参考两首文章:

  • TDD的iOS开发初步与Kiwi使用入门
  • Kiwi 使用上阶 Mock, Stub,
    参数捕获和异步测试

                                 

Designer News UI

每当编制Designer
News客户端代码之前,首先通过UI来打探任何App的大概。设计Designer News
UI的工具是Sketch,想获得Designer
News UI,请点击下载Designer New
UI。

Designer News Design.png

如若将有所的页面都相继说明什么编写,会于耗时间,所以才将登陆页面来证实自己是如何表现让开发iOS,但我会用所有项目的代码上盛传github。

      可见小木虫网页编码方式为gbk。

登陆界面

鉴于此项目简单又独自发一个口付出(大抵总人口开来说,采用Storyboard不易于代码合并),加上Storyboard可以可视化的添加UI组件和Auto
Layout的束缚,并且可以而且预览多只不同分辨率iPhone的法力,极大地提高开发界面效率。

Login.png

     1.2  XML包

登陆交互

登陆界面有Email输入框和密码输入框,当用户选中其他一个输入框时,左边对应之图标变成蓝色,同时会发pop动画表示用户准备要输入内容。
当用户并未输入有效之Email或密码格式时,用户是不可知点击登陆按钮,只有当用户输入有效之邮件和密码格式时,才会点击登陆按钮。

Login.gif

俺们可下RAC通过监听Text
Field的UITextFieldTextDidBeginEditingNotificationUITextFieldTextDidEndEditingNotification的通告来拍卖用户选中Email输入框和密码输入框时改变图标与显示的卡通片。

#pragma mark - Text Field notification
- (void)textFieldStartEndEditing
{
    // Respond to when email text start and end editing
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
        [self.emailImageView animate];
        self.emailImageView.image = [UIImage imageNamed:@"icon-mail-active"];
        self.emailTextField.background = [UIImage imageNamed:@"input-outline-active"];
    }];

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.emailTextField] subscribeNext:^(id x) {
        self.emailTextField.background = [UIImage imageNamed:@"input-outline"];
        self.emailImageView.image = [UIImage imageNamed:@"icon-mail"];
    }];

    // Respond to when password text start and end editing
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidBeginEditingNotification object:self.passwordTextField] subscribeNext:^(id x) {
        [self.passwordImageView animate];
        self.passwordTextField.background = [UIImage imageNamed:@"input-outline-active"];
        self.passwordImageView.image = [UIImage imageNamed:@"icon-password-active"];
    }];

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UITextFieldTextDidEndEditingNotification object:self.passwordTextField] subscribeNext:^(id x) {
        self.passwordTextField.background = [UIImage imageNamed:@"input-outline"];
        self.passwordImageView.image = [UIImage imageNamed:@"icon-password"];
    }];
}

当点击登陆按钮后,客户端向服务端发送验证请求,服务端验证了账户与密码后,用户就可以成功登陆。所以,接下要了解RESTful
API的基本概念和Designer News提供的RESTful API。

       R语言XML包
具有读取或者创造XML(HTML)文件的效用,可以本地文件为支撑HTTP 或者 FTP
,也供Xpath(XML路径语言)解析方法。此处函数htmlparse,将文件分析为XML或者HTML树,便于更加数据的提或者编辑。

Designer News API

        R命令:

RESTful API基本概念和计划

REST万事俱备是Representational State
Transfer,翻译过来就见层状态转化。要想的确清楚它们的义,从几个根本字入手:Resource,
Representation, State Transfer

  • ##### Resource(资源)

资源就是网及之实业,它可是文字、图片、声音、视频或平等种植服务。但网络有这样多资源,该怎么标识它们也?你可以为此URL(统一资源定位符)来唯一标识以及定位它们。只要获得资源对应的URL,你就算好看它们。

  • ##### Representation(表现层)

资源是如出一辙栽信息实体,它发生强表示法。比如,文本可以用.txt格式表示,也足以就此xml、json或html格式表示。

  • ##### State Transfer(状态转换)

客户端访问服务端,服务端处理了后返回客户端,在这个历程遭到,一般都见面惹数据状态的更动或者转换。
客户端操作服务端,都是通过HTTP协议,而于此HTTP协议被,有几单动词:GET,
POST, DELETEUPDATE

  • GET表示收获资源
  • POST表示新增资源
  • DELETE代表去资源
  • UPDATE表示更新资源

清楚RESTful核心概念后,我们来大概了解RESTful API设计以便可以看懂Designer
News提供API。就以Designer News获取Stories对许URL的一个例证来证明:
客户端请求
GET https://api-news.layervault.com/api/v1/stories?client_id=91a5fed537b58c60f36be1sdf71ed1320e9e4af2bda4366f7dn3d79e63835278

服务端返回结果(部分结果)

{
  "stories": [
    {
      "id": 46826,
      "title": "A Year of DuckDuckGo",
      "comment": "",
      "comment_html": null,
      "comment_count": 4,
      "vote_count": 17,
      "created_at": "2015-03-28T14:05:38Z",
      "pinned_at": null,
      "url": "https://news.layervault.com/click/stories/46826",
      "site_url": "https://api-news.layervault.com/stories/46826-a-year-of-duckduckgo",
      "user_id": 3334,
      "user_display_name": "Thomas W.",
      "user_portrait_url": "https://designer-news.s3.amazonaws.com/rendered_portraits/3334/original/portrait-2014-09-16_13_25_43__0000-333420140916-9599-7pse94.png?AWSAccessKeyId=AKIAI4OKHYH7JRMFZMUA&Expires=1459149709&Signature=%2FqqLAgqpOet6fckn4TD7vnJQbGw%3D",
      "hostname": "designwithtom.com",
      "user_url": "http://news.layervault.com/u/3334/thomas-wood",
      "badge": null,
      "user_job": "Online Designer at IDG UK",
      "sponsored": false,
      "comments": [
        {
          "id": 142530,
          "body": "Had no idea it had those customization settings — finally making the switch.",
          "body_html": "<p>Had no idea it had those customization settings — finally making the switch.</p>\\n",
          "created_at": "2015-03-28T18:41:37Z",
          "depth": 0,
          "vote_count": 0,
          "url": "https://api-news.layervault.com/comments/142530",
          "user_url": "http://news.layervault.com/u/3826/matt-soria",
          "user_id": 3826,
          "user_display_name": "Matt S.",
          "user_portrait_url": "https://designer-news.s3.amazonaws.com/rendered_portraits/3826/original/portrait-2014-04-12_11_08_21__0000-382620140412-5896-1udai4f.png?AWSAccessKeyId=AKIAI4OKHYH7JRMFZMUA&Expires=1459125745&Signature=%2BDdWMtto3Q10dd677sUOjfvQO3g%3D",
          "user_job": "Web Dood @ mattsoria.com",
          "comments": []
        },
  • 协议(protocol)
    用户以及API通信采用HTTPs协议
  • 域名(domain name)
    应当尽量部署及专用域名下https://api-news.layervault.com/,但有时候见面更加扩大为https://api-news.layervault.com/api
  • 版本(version)
    应该将API版本号v1放入URL
  • 路径(Endpoint)
    路径https://api-news.layervault.com/api/v1/stories表示API具体网址,代表网同样种植资源,所以无可知来动词,只有以名词来代表。
  • HTTP动词
    动词GET,表示于服务端获取Stories资源
  • 过滤信息(Filtering)
    ?client_id=91a5fed537b58c60f36be1sdf71ed1320e9e4af2bda4366f7dn3d79e63835278指定client_id的Stories资源
  • 状态码(Status Codes)
    服务器向客户端返回表示成功或黄的状态码,状态码列表请参考Status
    Code
    Definitions
  • 错误处理(Error handling)
    服务端处理用户请求失败后,一般还回error字段来代表错误信息

{
    error: "Invalid client id"
}

       
htmlParse(file,asText=T,encoding=”UTF-8″…) #参数file
即为XML或者HTML文件名或者text,asText参数是T指定file是text,encoding指定网页编码方式。

Designer News提供API

Designer News API
Reference供基于HTTP协和遵循RESTful设计之API,并且同意应用程序通过oAuth
2授权协议来收获授权权限来走访用户信息。

 

访问API工具

相似的话,在描绘访问服务端代码之前,我还见面因此Paw(下载地址)工具来测试API是否行得通;另一方面,用JSON文本保留服务端返回的多少,用于moco依傍服务端的劳务。至于怎么用moco模拟服务端,后面会教,现在经用户登录Designer
News
是例子介绍如何以Paw来测试API。
咱俩事先看Designer News提供访问用户登录的API

Designer News Login API.png

冲以上提供的音信,API的路子是https://api-news.layervault.com/oauth/token,参数有grant_typeusernamepasswordclient_secret。其中usernamepassword在Designer
News报才会获,而client_idclient_secret用发送email到news@layervault.com报名。使用Paw发送请求与服务端返回结果如下:

New Send Request.png

       这里我们用读取网页,并且用到拖欠网页的html树内容

Moco模拟服务端

Moco是一个足以轻松搭建测试服务器的工具。

        自定义函数download,输入strURL,strURL为网址,返回html树内容

怎么要效法服务端

作一个挪开发人员,有时由服务端开发进度慢,空有一个iPhone应用可表达不产生意向。幸好有矣Moco,只需要配置一下要与归数据,很快即好搭建一个拟服务,无需等待服务端支出好才能延续出。当服务端完成后,修改访问地址即可。

有时服务端API应该是什么法都还尚未明白,由于有了moco模拟服务,在支付进程被,可以不断调整API设计,搞明白真正好想如果的API是什么则的。就如此,在服务端代码还没有当真动手前,已经提供相同份真正满足自己待的API文档,剩下的便交劳务端照着API去贯彻即行了。

再有雷同栽状态就是,服务端已经写好了,剩下客户端还无就。由于moco是本地服务,访问速度比较快,所以经过下moco来模拟服务端,这样不但可以加强客户端的访问速度,还增强网络层测试代码访问速度的平静,Designer
News就是这般情况。

            download <- function(strURL){
              h <- basicTextGatherer( )# 查看服务器返回的峰信息
              txt <- getURL(strURL, headerfunction =
h$update,.encoding=”gbk”) ## 字符串形式
               htmlParse(txt,asText=T,encoding=”gbk”)     
#挑选gbk进行网页的剖析
             }

哪下Moco模拟服务

亚、获得一个网页所有的URL

安装

要是你是下Mac或Linux,可以品尝一下手续:

  1. 规定你安装JDK 6以上
  2. 下载脚本
  3. 将她坐落你的$PATH路径
  4. 安装它好推行(chmod 755 ~/bin/moco)

今日公得运作一下命令测试安装是否成

  1. 修配置文件foo.json,内容如下:

[
      {
        "response" :
          {
            "text" : "Hello, Moco"
          }
      }
]
  1. 运行Moco HTTP服务器
    moco start -p 12306 -c foo.json
  2. 打开浏览器访问http://localhost:12306,你扭曲看见”Hello, Moco”

   
有时候我们用上每个网页上的子链接取分析数据,这个时段可就此到XML包之getHTMLLinks函数。

布局服务

是因为发生时分服务端返回的数码较多,所以将服务端响应的数据独立在一个JSON文件中。以登陆为例,将数据存放于login_response.json

{
    "access_token": "4422ea7f05750e93a101cb77ff76dffd3d65d46ebf6ed5b94d211e5d9b3b80bc",
    "token_type": "bearer",
    "scope": "user",
    "created_at": 1428040414
}

倘用呼吁uri路径,方法(method)和参数(queries)等配备在login_conf.json文件中

[
  {
    "request" :
      {
        "uri" : "/oauth/token",
        "method" : "post",
        "queries" : 
          {
            "grant_type" : "password",
            "username" : "liuyaozhu13hao@163.com",
            "password" : "freedom13",
            "client_secret" : "53e3822c49287190768e009a8f8e55d09041c5bf26d0ef982693f215c72d87da",
            "client_id" : "750ab22aac78be1c6d4bbe584f0e3477064f646720f327c5464bc127100a1a6d"
          }
      },
    "response" :
      {
        "file" : "./Login/login_response.json"
      }
  }
]

不晓得有无来留意到面uri路径不是咸路线http://localhost:12306/oauth/token,因为协议默认是http,而且一般运行于本机localhost,所以在起步拟服务经常止需要点名端口12306哪怕行。想愈详实摸底哪些布置,请查阅官网的HTTP(s)
APIs
还有一个要配备地方就是,由于实在支出被必不止一个客户端请求,所以还用一个布置文件settings.json来含有很有请。

[
    {
        "include" : "./Story/stories_conf.json"
    },
    {
        "include" : "./Login/login_conf.json"
    },
    {
        "include" : "./Story/story_upvote_conf.json"
    }
]

    R命令:

启航服务

用路径跳反至DesignerNewsForObjc/DesignerNewsForObjcTests/JSON目,找到settings.json文件,使用命令执行来启动服务:
moco start -p 12306 -g settings.json

        getHTMLLinks(doc,  xpQuery =
“//a/@href”…)
#doc为剖后的HTML树文件,xpQuery指定想匹配的Xpath元素(下面会详细讲一些Xpath基础)。

应用Paw验证是否配备成功

Send request to Local Server.png

    此处我们得取小木虫“导师招生”页面下的持有话题链接。

行事使开发(BDD)

    2.1
首先我们而取导师招生的首先页,第二页,第三页,甚至到结尾一页的网址。

何以用BDD

不知情诸位在编排测试的时刻,有没有来考虑了一个题材:我应当测试什么?要回这个题目并无是那简单,在没有得到答案之前,你要继续按照卿的想法编写测试。
-(void)testValidateEmail;
比如这么的测试,存在一个素问题。它不见面报告你当会来什么,也非见面预期实际会时有发生什么。还有,当她来错误时,不见面唤醒您于哪有错误,错误的原故是什么,因此而得深刻代码才能够明了失败的来由。这样就算得大量外加和莫必要之体会负荷。
这时候BDD出现了,帮助开发者确定当测试什么,它提供DSL(Domain-specific
language,
域特定语言),测试用例都遵循三段落式Given-When-Then的描述,清晰地表达测试用例是测试什么样的目标要数据结构,在根据什么上下文或现象,然后做出什么响应。
用,我们当关爱行为,而不是测试。那行为具体是什么?当您设计app里面的内目标时,它的接口定义方法及其依赖关系,这些点子和依赖关系决定了卿的靶子如何和其它对象交互,以及它们的效能是啊,定义你的对象的行为

        导师招生首页

BDD过程

表现令开发大概三单步骤:

  1. 选最为要之行,并编写行为的测试文件。此时,由于测试对象的好像还未曾编制,所以编译失败。创建测试目标的类并编制类的野鸡实现,让编译通过。
  2. 实现于测试类的一言一行,让测试通过。
  3. 若果发现代码中发出更代码,重构吃测试类来扫除再

假定临时无晓得里面步骤细节,没有涉及,继续朝下看,后面来例子介绍来扶持而懂三独步骤的意义。

                       

登陆验证

       导师招生第二页,第三页。

网络访问层

                     

DesignerNewsURL

DesignerNewsURL接近包装网络访问URL

#import <Foundation/Foundation.h>

extern NSString* const baseURL;
extern NSString* const clientID;
extern NSString* const clientSecret;

@interface DesignerNewsURL : NSObject

+ (NSString*)loginURLString;
+ (NSString*)stroiesURLString;
+ (NSString*)storyIdURLStringWithId:(NSInteger)storyId;
+ (NSString*)storyUpvoteWithId:(NSInteger)storyId;
+ (NSString*)storyReplyWithId:(NSInteger)storyId;
+ (NSString*)commentUpvoteWithId:(NSInteger)commentId;
+ (NSString*)commentReplyWithId:(NSInteger)commentId;

@end

此还起只技巧就是在DesignerNewsURL.m落实文件来个规格编译,判断是于测试环境还是产品环境来决定baseURL的价值,可以生有益在测试环境与产品环境相互切换。

#ifndef TEST
NSString* const baseURL = @"https://api-news.layervault.com";
#else
NSString* const baseURL = @"http://localhost:12306";
#endif

NSString* const clientID = @"750ab22aac78be1c6d4bbe584f0e3477064f646720f327c5464bc127100a1a6d";
NSString* const clientSecret = @"53e3822c49287190768e009a8f8e55d09041c5bf26d0ef982693f215c72d87da";

                  

作为让开发LoginClient

以编制代码之前,我们应有先想想如何筹划LoginClient恍如。首先根据Single
responsibility
principle(责任单一原则),LoginClient重要担负用户登录的纱访问。需要提供一个接口,只要加用户称(username)和密码(password),用户就能登录,由于自己是行使RAC来处理回来结果,所以这个接口返回RACSignal对象。

  • 开创一个LoginClientkiwi文件,编写对应行为。

Create LoginClient 1.png

Create LoginClient 2.png

SPEC_BEGIN(LoginClientSpec)

describe(@"LoginClient", ^{

    context(@"when user input correct username and password", ^{
      __block RACSignal *loginSignal;

      beforeEach(^{
          NSString *username = @"liuyaozhu13hao@163.com";
          NSString *password = @"freedom13";
          loginSignal = [LoginClient loginWithUsername:username password:password];
      });

      it(@"should return login signal that can't be nil", ^{
          [[loginSignal shouldNot] beNil];
      });

      it(@"should login successfully", ^{
          __block NSString *accessToken = nil;

          [loginSignal subscribeNext:^(NSString *x) {
              accessToken = x;
              NSLog(@"accessToken = %@", accessToken);
          }error:^(NSError *error) {
              [[accessToken shouldNot] beNil];
          } completed:^{
              [[accessToken shouldNot] beNil];
          } ];
      });

    });
});

SPEC_END

因三段式Given-When-Then叙述,上面代码我们可以知道呢:在吃定LoginClient对象,当用户输入是的用户称以及密码时,应该登录成功。
这儿,由于还没创建LoginClient仿佛,所以会无经过编译,创建LoginClient恍如,并编写它的伪实现,让LoginClientSpec.m由此编译。

LoginClient.h.png

LoginClient.m.png

运行测试,测试失败。

LoginClient Failed.png

  • 落实LoginClient,通过其测试

LoginClient.m .png

LoginClient Pass Test.png

  • 鉴于无冗余代码,无需重构

       
发现首页网址是http://muchong.com/html/f430.html,余下的网址符合http://muchong.com/html/f430\_ 
+   第几页   +.html 

Model层

鉴于这次登陆请求服务端返回数据比较简单,只是得到access_token许段数据,所以未需model来照和存储数据。不过以赢得多独Stories时,就会使到model来处理。

        于是网址我们得以手动编辑。

Controller与ViewModel层

controller大凡拍卖用户交互的输入,通常自己都见面以拍卖用户交互的逻辑、数据绑定和数目校验都交给ViewModel来精简controller代码,同时最深程度地复用业务逻辑的代码。
咱先行想起用户登陆时之步骤:1.
用户优先输入email和密码,只有email和密码可格式要求时才能够点击按钮。2.
用户成功登陆后,跳反到故事列表主页。
俺们先分析一下什么样促成步骤1,
想要本着email和密码进行认证,必须要监听它们简单独价值的转,所以需要对emailTextFieldpasswordTextField使用RAC进行数量绑定。

创建LoginViewControllerSpeckiwi文件,测试绑定行为代码如下:

SPEC_BEGIN(LoginViewControllerSpec)

describe(@"LoginViewController", ^{
    __block LoginViewController *controller;

    beforeEach(^{
        controller = [UIViewController loadViewControllerWithIdentifierForMainStoryboard:@"LoginViewController"];
        [controller view];
    });

    afterEach(^{
        controller = nil;
    });

    describe(@"Email Text Field", ^{
        context(@"when touch text field", ^{
            it(@"should not be nil", ^{
                [[controller.emailTextField shouldNot] beNil];
            });
        });

        context(@"when text field's text is hello", ^{
            it(@"shoud euqal view model's email property", ^{
                controller.emailTextField.text = @"hello";
                [controller.emailTextField sendActionsForControlEvents:UIControlEventEditingChanged];
                [[controller.viewModel.email should] equal:@"hello"];
            });
        });
    });

    describe(@"Password Text Field", ^{
        context(@"when touch text field", ^{
            it(@"should not be nil", ^{
                [[controller.passwordTextField shouldNot] beNil];
            });
        });

        context(@"when text field' text is hello", ^{
            it(@"should equal view model's password property", ^{
                controller.passwordTextField.text = @"hello";
                [controller.passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

                [[controller.viewModel.password should] equal:@"hello"];
            });
        });
    });
});

SPEC_END

此发生个别只举足轻重点,一个凡是从Storyboard中加载controller,否则不克取emailTextField和password,如果采用手写UI代码就未待了。另一个即便是emailTextField或passwordTextField必须调用sendActionsForControlEvents:UIControlEventEditingChanged计,才会触发textField的text属性改变。

编译失败后,在LoginViewController.m编写- (void)bindViewModel办法通过测试

RAC(self.viewModel, email) = self.emailTextField.rac_textSignal;
RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal;

心想事成得了数据绑定行为后,接下去要多少校验,交给LoginViewModel来处理。创建LoginViewModelSpec.m文件,提供emailpassword属性给LoginViewModel,返回验证结果的RACSignal,测试证明行为代码如下:

SPEC_BEGIN(LoginViewModelSpec)

describe(@"LoginViewModel", ^{
    // Initialize
    __block LoginViewModel *viewModel;

    beforeEach(^{
        viewModel = [[LoginViewModel alloc] init];
    });

    afterEach(^{
        viewModel = nil;
    });

    context(@"when email and password is valid", ^{
        it(@"should get valid signal", ^{
            viewModel.email = @"liuyaozhu13hao@163.com";
            viewModel.password = @"123456";

            __block BOOL result;

            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) should] beYes];
            }];
        });
    });

    context(@"when email is valid, but password is invalid", ^{
        it(@"should get invalid signal", ^{
            viewModel.email = @"liuyaozhu13hao@163.com";
            viewModel.password = @"1";

            __block BOOL result;

            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) shouldNot] beYes];
            }];
        });
    });

    context(@"when password is valid, but email is invalid", ^{
        it(@"should get invalid signal", ^{
            viewModel.email = @"liuyaozhu";
            viewModel.password = @"123456";

            __block BOOL result;
            [[viewModel checkEmailPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            } completed:^{
                [[theValue(result) shouldNot] beYes];
            }];
        });
    });
});

SPEC_END

编译失败后(已经创造LoginViewModel类),添加- (RACSignal*)checkEmailPasswordSignal并贯彻认证数据,通过测试

- (RACSignal*)checkEmailPasswordSignal
{
    RACSignal* emailSignal = RACObserve(self, email);
    RACSignal* passwordSignal = RACObserve(self, password);

    return [RACSignal combineLatest:@[ emailSignal, passwordSignal ] reduce:^(NSString* email, NSString* password) {
        BOOL result = [email isValidEmail] && [password isValidPassword];

        return @(result);
    }];
}

最终要以LoginViewModel缔造属性为loginButtonCommandRACCommand来拍卖点击登陆按钮的互。在LoginViewControllerSpec.m测试loginButton.rac_command勿可知吧空

describe(@"Login Button", ^{
      context(@"when load view", ^{
            it(@"should be not nil", ^{
                [[controller.loginButton shouldNot] beNil];
            });

            it(@"should have rac command that not be nil", ^{
                [[controller.loginButton.rac_command shouldNot] beNil];
            });
      });
 });

测试失败,在LoginViewController.m编写- (void)bindViewModel艺术以下代码有

self.loginButton.rac_command = self.viewModel.loginButtonCommand;

LoginViewModel.m延期初始化loginButtonCommand属性

#pragma mark - Lazy initialization
- (RACCommand*)loginButtonCommand
{
    if (!_loginButtonCommand) {
        _loginButtonCommand = [[RACCommand alloc] initWithEnabled:[self checkEmailPasswordSignal] signalBlock:^RACSignal * (id input) {
            self.active = YES;

            return [[LoginClient loginWithUsername:self.email password:self.password] doNext:^(NSString *token) {
                self.active = NO;
                // Save the token
                [LocalStore saveToken:token];
                // Dismiss view controller and fetch data, reload
                self.dismissBlock();
            }];
        }];
    }

    return _loginButtonCommand;
}

经测试,完成登陆基本流程,至于登陆成功后怎么回到故事列表页面,这里不详细介绍,各位好经过翻阅工程代码即便可获得答案。

        strURLs=”http://muchong.com/html/f430.html”

总结

多年来一段时间都更拘留关于敏捷开发之书(用户故事以及快快方法,硝烟中的Scrum和XP,
解析极限编程),对便捷开发很感兴趣,但意识很少公司或者博客介绍如何实行敏捷开发iOS,所以当网上收集一些资料,发现出许多精的履行(测试驱动开发,重构,持续集成测试,增量设计,增量计划)值得去读,通过祥和对高速开发中各种实践的敞亮来还写这Designer
News,这个Designer
News功能还没有通就,希望各位看了就首稿子尝试为这样方式来就总体app。如果我不怎么意见或执行理解有无意,请各位多多指导。

        n=50

扩大阅读

  • ReactiveCocoa
    ReactiveCocoa –
    iOS开发之初框架
    ReactiveCocoa2实战
    ReactiveCocoa Essentials: Understanding and Using
    RACCommand
    Test with
    Reactivecocoa
  • Kiwi
    TDD的iOS开发初步与Kiwi使用入门
    Kiwi 使用上阶 Mock, Stub,
    参数捕获和异步测试
  • RESTful API
    理解RESTful架构
    RESTful API
    设计指南
    理解OAuth
    2.0
    SSL/TLS协议运行机制的概述
  • Moco
    Moco能集成测试,还能够活动支付;能前端开发,还能够模拟Web服务器!
  • 测试
    行事使得开发
    XCTest
    测试实战
    倚注入
    不好的测试
    换成测试: Mock, Stub
    和其余

        strURLs <-
c(strURLs,paste(rep(“http://muchong.com/html/f430\_",n),c(2:n),".html",sep=””))

        strURLs包括了颇具1交50页导师招生网页的网址。

    2.2取各国一样页导师招生中大多独话题之链接

             

       
以导师招生页面下,有成百上千话题,我们用获得各个话题之链接。

        用getHTMLLinks函数查看导师招生中有URL,再比话题网址。

 

       

        http://muchong.com/html/201702/11075436.html

        发现话题网址是成成分是http://muchong.com/ +
html/201702/11075436.html 类似的URL

        这时我动用先打师资招生网页提取所有URL,再配合配 html *
.html格式的URL,最后还前面加上http://muchong.com/ 的策略。

        自定义greg函数用于正则匹配,并且赢得匹配到之字符串。
            greg <- function(pattern,istring){
                gregout <- gregexpr(pattern,istring)  
#pattern为配合模式,istring为急需匹配的字符串
               
substr(istring,gregout[[1]],gregout[[1]]+attr(gregout[[1]],’match.length’)-1)
             }

         自定义extradress函数,用于取strURL网页的受之 URL
,最后处理回来各个话题网页的链接。

            extradress <- function(strURL){
                 prefix <- “http://muchong.com/”
                 pattern <- “html/[0-9/]+.html”
                 links <- getHTMLLinks(strURL)
                 needlinks <- gregexpr(pattern,links)
                 needlinkslist <- list()
                for (i in which(unlist(needlinks)>0)){
                    preadress <-
substr(links[i],needlinks[[i]],needlinks[[i]]+attr(needlinks[[i]],’match.length’)-1)
                    needlinkslist<-
c(needlinkslist,list(preadress))
                   adresses <-
lapply(needlinkslist,function(x)paste(prefix,x,sep=””))
                 }
                return (adresses)
                 }

     

其三、从HTML树被获我们所假设的数量

    3.1 XML文档基本知识

    脚是聊木虫的有html:

 

   

 

  
html为根元素,head和body是html的子元素,div是body的子元素,div有性id,style,属性后面对应着属于性值。“小木虫—“一行是p元素的公文内容。

    3.2 获得某元素的始末

       此处用到XML包着之getNodeSet函数,getNodeSet函数

        R命令:

        getNodeSet(doc, path…)
#doc 就是html树文件对象,path
就是素路径。可以用/从根元素一偶发指定路线,也得用//直接固定及某平等叠元素。

        例如要一定及html下的body下之div,path
即为/html/body/div,也可//body/div直接从body开始定点。返回列表,如果一定及大半独要素,将赶回多单因素的列表。此次我们设定为到网页的话题内容:

 

                     

     我们这里一直定位及p元素,再于列表中筛选。

     先输入指令

      getNodeSet(doc,’//p’)

 

     

 

      getNodeSet(doc,’//p’)[[2]]不畏是咱需要之情节。

 

     

     

     
但是回到的结果是独目标,要扭转吗字符串要用到函数xmlValue获得第一素值。

       xmlValue(x…) #
x就是getNodeSet得到的对象

       此处

  xmlValue(getNodeSet(a,'//p')[[2]]) 得到我们所要的内容


  


   此时,我们获得了每一个话题的内容,我们就可以从内容中提取有效信息,是否招调剂,大学名,导师名字,研究方向,联系人,邮箱,电话等。

季、从小木虫获取调剂信息实例

   
我师妹是生物正式的急需调剂的学生,现在要从小木虫网站提取别人宣布之信息,做成一个报表形式,便于筛选查看和发送邮件。

   以下是满代码内容

 

library(RCurl)
library(XML)

download <- function(strURL){
    h <- basicTextGatherer()# 查看服务器返回的峰信息
    txt <- getURL(strURL, headerfunction = h$update,.encoding=”gbk”)
## 字符串形式
    htmlParse(txt,asText=T,encoding=”gbk”)     
#挑选gbk进行网页的剖析
}

extradress <- function(strURL){
  prefix <- “http://muchong.com/”
  pattern <- “html/[0-9/]+.html”
  links <- getHTMLLinks(strURL)
  needlinks <- gregexpr(pattern,links)
  needlinkslist <- list()
  for (i in which(unlist(needlinks)>0)){
    preadress <-
substr(links[i],needlinks[[i]],needlinks[[i]]+attr(needlinks[[i]],’match.length’)-1)
    needlinkslist<- c(needlinkslist,list(preadress))
    adresses <-
lapply(needlinkslist,function(x)paste(prefix,x,sep=””))
  }
  return (adresses)
}

gettopic <- function(doc){
    xmlValue(getNodeSet(doc,’//p’)[[2]])
}

greg <- function(pattern,istring){
    gregout <- gregexpr(pattern,istring)
   
substr(istring,gregout[[1]],gregout[[1]]+attr(gregout[[1]],’match.length’)-1)
}

getinf <- function(topic){
pattern1 <-
“招[\u4E00-\u9FA5]+[0-9-]*[\u4E00-\u9FA5]*[:、;,,;]*[\u4E00-\u9FA5]*[:、;,,;]*[\u4E00-\u9FA5]*[:、;,,;]*[\u4E00-\u9FA5]*[:、;,,;]*[\u4E00-\u9FA5]*(研究生)|(调剂)”
pattern2 <- “([\u4E00-\u9FA5]*课题组|[\u4E00-\u9FA5]*团队)”
 
pattern21 <- “[\u4E00-\u9FA5]*[:、;,,;]*(教授|博士)”
pattern3 <-
“[\u4E00-\u9FA5]*[:、;,,;]*[-A-Za-z0-9_.%]+@[-A-Za-z0-9_.%]+\\.[A-Za-z]+[.A-Za-z]*”
    #匹配配@163.com类要@abc.edu.cn两近乎邮箱
pattern4 <- “[\u4E00-\u9FA5]+老师”  #匹配有老师
pattern5 <-
“[\u4E00-\u9FA5]*[::]*1[3,5,8]{1}[0-9]{1}[0-9]{8}|0[0-9]{2,3}-[0-9]{7,8}(-[0-9]{1,4})?”
#相当联系人和号码
pattern6 <-
“(主|从事)*[\u4E00-\u9FA5]*(的研究|方向)为*[:、;,,;]*[\u4E00-\u9FA5]*”
pattern7 <- “[\u4E00-\u9FA5]+(大学|学院|研究院|研究所)”
pattern8
<-“[-A-Za-z0-9_.%]+@[-A-Za-z0-9_.%]+\\.[A-Za-z]+[.A-Za-z]*”
#准匹配邮箱

cate <- greg(pattern1,topic)
proj <- greg(pattern2,topic)
PI <- greg(pattern21,topic)
email <- greg(pattern3,topic)
man <- greg(pattern4,topic)
phone <- greg(pattern5,topic)
direc <- greg(pattern6,topic)
univ <- greg(pattern7,topic)
print(cate)
if (greg(“(分子|生物|植物|细胞|医学|动物|水)+”,topic) !=””){
    if (man ==”” && proj != “”){
        man <- unlist(strsplit(proj,”课题组”)[1])
    }
 
    if (email != “”){
      email <- greg(pattern10,email)
    }
    
   
data.frame(“类别”=cate,”大学”=univ,”课题”=proj,”PI”=PI,”联系人”=man,”邮箱”=email,”方向”=direc,”电话”=phone)
}
else{
  return(“”)
}
}

strURLs=”http://muchong.com/html/f430.html”
n=50
dat <-
data.frame(“URL”=”URL”,”类别”=”类别”,”大学”=”大学”,”课题”=”课题”,”PI”=”PI”,”联系人”=”联系人”,”邮箱”=”邮箱”,”方向”=”方向”,”电话”=”电话”)
strURLs <-
c(strURLs,paste(rep(“http://muchong.com/html/f430\_",n),c(2:n),".html",sep=””))
output1 <- “a2017.2.21.txt” #不处理多少,用于更处理
output2 <- “b2017.2.21.txt” #更为筛选的多寡,用于查看

for ( strURL in strURLs){
    adresses <- extradress(strURL)
    for (adress in adresses){
      message(adress)
      doc <- download(adress)
      topic <- gettopic(doc)
      inf <- getinf(topic)
      if (inf != “”){
        URL <- data.frame(“URL”=adress)
        inf <- cbind(URL,inf)
        dat<- rbind(dat,inf)
      }
    }
}

write.table(dat, file = output1, row.names = F, col.names=F,quote = F,
sep=”\t”)  # tab 分隔的文书
message(“完成!”)

dat <- read.table(output1,sep=”\t”,header=T)
dat <- dat[dat$邮箱, ] #去没有邮箱数据
dat <- dat[!duplicated(dat$邮箱), ]  #删除重复邮箱数据
dat$index <- as.numeric(rownames(dat))
dat <- dat[order(dat$index,decreasing=F),]
#拿乱序后的数量重复按index排序
dat$index <- NULL
write.table(dat, file = output2, row.names = F, col.names=F,quote = F,
sep=”\t”)  # tab 分隔的公文
message(“完成!”)

 

 

终极祝福所有考研人且能够成为心仪之学府选用!

 

 

参考资料:

Rcurl包 :https://cran.r-project.org/web/packages/RCurl/RCurl.pdf

XML包:https://cran.r-project.org/web/packages/XML/XML.pdf

XML基本知识:http://www.cnblogs.com/thinkers-dym/p/4090840.html