webmagic-coreプロセスとコンポーネントのソースコード分析

概要:WebMagicは構造が分かれDownloaderPageProcessorSchedulerPipeline四つの成分は、スパイダーが互いにによってそれらを整理。これらの4つのコンポーネントは、クローラーのライフサイクルにおけるダウンロード、処理、管理、および永続化の機能に対応しています。

WebmagicのスタートアップクラスはSpiderです。run()メソッドが実行されると、さまざまなコンポーネントが初期化され、ループで実行されます。リクエストはスケジューラから取得され、runnableにパッケージ化され、処理されます。このプロセスでは、Schedulerコンポーネントが重複排除と保留中のリクエストの読み込みを担当し、Downloaderコンポーネントがリクエスト結果のダウンロードとページクラスへのパッケージ化を担当し、pageProcessorコンポーネントがページ結果の処理を担当し、URLを追加できます。ページから収集し、page.resultItemsとともにインストールする必要があります。処理された結果データは、結果を処理するためにPipelineコンポーネントに送信されます。

具体的な使用方法については、公式ドキュメントhttp://webmagic.io/docs/zh/を参照するか、関連するケースを検索してください。この記事では、主にwebmagicのソースコードを分析します。

    1.プロセス分析

1.主な方法    

public void run() {
    	// 检查运行状态,并且compareAndSet成运行
        checkRunningStat();
        // 初始化组件
        initComponent();
        logger.info("Spider {} started!",getUUID());
        // 当前线程非中断,运行状态持续运行。
        while (!Thread.currentThread().isInterrupted() && stat.get() == STAT_RUNNING) {
            final Request request = scheduler.poll(this);
            if (request == null) {
                if (threadPool.getThreadAlive() == 0 && exitWhenComplete) {
                    break;
                }
                // wait until new url added
                waitNewUrl();
            } else {
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                        	// 处理请求,下载page,pageProcess处理page,pipeline处理page
                            processRequest(request);
                            onSuccess(request);
                        } catch (Exception e) {
                            onError(request);
                            logger.error("process request " + request + " error", e);
                        } finally {
                            pageCount.incrementAndGet();
                            signalNewUrl();
                        }
                    }
                });
            }
        }
        // 设置状态为停止
        stat.set(STAT_STOPPED);
        if (destroyWhenExit) {
            close();
        }
        logger.info("Spider {} closed! {} pages downloaded.", getUUID(), pageCount.get());
    }

      上記からわかるように、Spiderはさまざまなコンポーネントの実行を調整することです。コアロジックは、スケジューラからの要求を受け取り、それをスレッドプールによって実行されるタスクにパッケージ化することです。いくつかの主要なコンポーネントがそれらを担当します。このプロセスにおけるそれぞれの責任。

   2)コンポーネントinitComponentを初期化します

protected void initComponent() {
        if (downloader == null) { // 默认Downloader为HttpClientDownloader
            this.downloader = new HttpClientDownloader();
        }
        if (pipelines.isEmpty()) { // 默认Pipeline为ConsolePipeline
            pipelines.add(new ConsolePipeline());
        }
        downloader.setThread(threadNum);
        if (threadPool == null || threadPool.isShutdown()) {
            if (executorService != null && !executorService.isShutdown()) {
                threadPool = new CountableThreadPool(threadNum, executorService);
            } else {
                threadPool = new CountableThreadPool(threadNum); // 线程池为CountableThreadPool包装
            }
        }
        if (startRequests != null) { // 初始添加请求到scheduler, 默认scheduler为QueueScheduler
            for (Request request : startRequests) {
                addRequest(request);
            }
            startRequests.clear();
        }
        startTime = new Date();
    }

 いくつかのコアコンポーネントダウンローダーの初期実装-HttpClientDownloader、パイプライン-ConsolePipeline、スケジューラー-QueueSchedulerpageProcessor-手動で作成。

 3.リクエストメソッドprocessRequestを処理しています

private void processRequest(Request request) {
        Page page = downloader.download(request, this); // 下载-即通过HttpClient下载页面,并且构建Page对象。
        if (page.isDownloadSuccess()){ // 下载成功
            onDownloadSuccess(request, page);
        } else { // 失败循环
            onDownloaderFail(request);
        }
    }

 //ダウンロードは正常に処理されました

    private void onDownloadSuccess(Request request, Page page) {
        if (site.getAcceptStatCode().contains(page.getStatusCode())){
            pageProcessor.process(page); // 先用pageProcessor处理page
            extractAndAddRequests(page, spawnUrl); // 提取添加的request
            if (!page.getResultItems().isSkip()) { // 如果没有设置isSkip
                for (Pipeline pipeline : pipelines) { 
                    pipeline.process(page.getResultItems(), this); // 多个pipelines处理结果
                }
            }
        }
        sleep(site.getSleepTime());
        return;
    }

  ダウンロードを成功させるための処理ロジックは、pageProcessorを介してページを処理し、addTragetRequestを抽出してスケジューラーに追加し、isSkipが設定されているかどうかを判断し、設定されていない場合は、複数のパイプラインを使用して結果を処理します。

///ダウンロード失敗の処理

 private void onDownloaderFail(Request request) {
        if (site.getCycleRetryTimes() == 0) { // 如果错误循环重试次数为0,那么只停顿
            sleep(site.getSleepTime());
        } else { 
            doCycleRetry(request); // 循环重试---详见下一代码片段
        }
    }

//ループの再試行

    private void doCycleRetry(Request request) {
        Object cycleTriedTimesObject = request.getExtra(Request.CYCLE_TRIED_TIMES);
        if (cycleTriedTimesObject == null) { // 循环重试次数0时,clone一个新请求添加设置次数1
            addRequest(SerializationUtils.clone(request).setPriority(0).putExtra(Request.CYCLE_TRIED_TIMES, 1));
        } else {
            int cycleTriedTimes = (Integer) cycleTriedTimesObject;
            cycleTriedTimes++; // 重试1次是,clone一个新请求,将附加参数递增。
            if (cycleTriedTimes < site.getCycleRetryTimes()) { 
                addRequest(SerializationUtils.clone(request).setPriority(0).putExtra(Request.CYCLE_TRIED_TIMES, cycleTriedTimes));
            }
        }
        sleep(site.getRetrySleepTime());
    }

 

 

上記のプロセスでは、公式バージョンよりも2つの詳細なフローチャートが描かれています

 

2、成分分析-スケジューラー(スケジューリング)

スケジューラスケジューラは、クロールされるURLの管理とスケジュール設定、および一部の重複排除作業を担当します。WebMagicは、デフォルトでLinkedBlockingQueueメモリキューを提供してURLを管理し、コレクションを使用して重複を削除します。また、分散管理のためのRedisの使用もサポートしています。主なスケジューラの実装は次のとおりです。

DuplicateRemovedScheduler:スケジューラの基本クラスである組み込みのDuplicatedRemoverは、HashSetDuplicateRemoverとして実装されます。
QueueSchedulerLinkedBlockingQueueを介して収集される接続を格納するキュースケジューラ。
FileCacheQueueScheduler:ファイルキャッシュキュースケジューラ。キューに基づいて、すべてのURLとポーリングの場所がファイルを介して記録されます。一時的に停止した場合は、ファイルを介して収集を再開できます。
RedisScheduler:Redisを使用してクロールキューを保存します。listはキューキューを保存し、setは重複排除、ハッシュ、および要求された追加データの保存を担当します。

2.1 DuplicateRemovedScheduler

   	public abstract class DuplicateRemovedScheduler implements Scheduler {
			// 默认重复器
		    private DuplicateRemover duplicatedRemover = new HashSetDuplicateRemover();
			
		    public DuplicateRemover getDuplicateRemover() {
		        return duplicatedRemover;
		    }
			
		    public DuplicateRemovedScheduler setDuplicateRemover(DuplicateRemover duplicatedRemover) {
		        this.duplicatedRemover = duplicatedRemover;
		        return this;
		    }
			
			// 是post请求或循环重试或不重复才添加。
		    @Override
		    public void push(Request request, Task task) {
		        logger.trace("get a candidate url {}", request.getUrl());
		        if (shouldReserved(request) || noNeedToRemoveDuplicate(request) || !duplicatedRemover.isDuplicate(request, task)) {
		            logger.debug("push to queue {}", request.getUrl());
		            pushWhenNoDuplicate(request, task);
		        }
		    }
			// 循环重试的请求不进行判断
		    protected boolean shouldReserved(Request request) {
		        return request.getExtra(Request.CYCLE_TRIED_TIMES) != null;
		    }
			// POST请求不进行判断
		    protected boolean noNeedToRemoveDuplicate(Request request) {
		        return HttpConstant.Method.POST.equalsIgnoreCase(request.getMethod());
		    }
			// 子类负责实现
		    protected void pushWhenNoDuplicate(Request request, Task task) {
		
		    }
		}

 2.2QueueSchedulerメモリキューのURLスケジューリング

QueueSchedulerの実装では、内部のLinkedBlockingQueueがURLの取得または取得を担当します。

2.3 RedisSchedulerRedis分散URLスケジューリング

1)メンバー変数

protected JedisPool pool;
private static final String QUEUE_PREFIX = "queue_"; // 构建list队列的key的前缀
private static final String SET_PREFIX = "set_"; // 构建set的key的前缀
private static final String ITEM_PREFIX = "item_"; // 构建hash的key的前缀

public RedisScheduler(JedisPool pool) {
	this.pool = pool;
	setDuplicateRemover(this); // 设置重复的实现为自身。
}

	protected String getSetKey(Task task) {
		return SET_PREFIX + task.getUUID();
	}

	protected String getQueueKey(Task task) {
		return QUEUE_PREFIX + task.getUUID();
	}

	protected String getItemKey(Task task) {
		return ITEM_PREFIX + task.getUUID();
	}

上記はメンバー変数です。URLキューはキューを介して保存され、セットは重複排除を担当し、ハッシュは補助データの保存を担当します。

さらに、コンストラクターはje​​disPoolを渡すかビルドし、DuplicateRemover自体を実現し、このオブジェクトのduplicateRemoverフィールドに設定しました。

2)重複排除の実装

public boolean isDuplicate(Request request, Task task) {
				Jedis jedis = pool.getResource();
				try { 
					return jedis.sadd(getSetKey(task), request.getUrl()) == 0;
				} finally {
					pool.returnResource(jedis);
				}
			}   	

3)プッシュ 

      public void pushWhenNoDuplicate(Request request, Task task) {
                Jedis jedis = pool.getResource();
                try {
                    jedis.rpush(getQueueKey(task), request.getUrl());
                    if (request.getExtras() != null) {
                        String field = DigestUtils.md5Hex(request.getUrl());
                        String value = JSON.toJSONString(request);
                        jedis.hset(getItemKey(task), field, value);
                    }
                } finally {
                    pool.returnResource(jedis);
                }
            }
        

4)プール

  public Request poll(Task task) {
            Jedis jedis = pool.getResource();
                try {
                    String url = jedis.lpop(getQueueKey(task));
                    if (url == null) {
                        return null;
                    }
                    String key = getItemKey(task);
                    String field = DigestUtils.md5Hex(url);
                    byte[] bytes = jedis.hget(key.getBytes(), field.getBytes());
                    if (bytes != null) {
                        Request o = JSON.parseObject(new String(bytes), Request.class);
                        return o;
                    }
                    return new Request(url);
                } finally {
                    pool.returnResource(jedis);
                }
            }

    2.3 FileCacheQueueScheduler 

FileCacheQueueSchedulerは、ファイルキャッシュキュースケジューラです。主に2つのファイルを使用して、すべてのURLと使用されたカーソルの数を記録します。initメソッドは、プッシュまたはポーリングの実行時に初期化され、初期化中に2つのファイルが読み取られます。すべてのURLファイルに保存されているURLはURLに追加され、カーソルの数よりも大きいURLはリクエストとしてパッケージ化され、キューに追加されます。

 また、FileCacheQueueSchedulerには、初期化時に2つのprintWriterが作成され、プッシュ(インクリメンタル)時に同時にこのライターに追加されたURLを書き込む役割があり、ポーリング時に、ポーリングの数がカーソルを介して記録されるため、回復時にカーソルを介して判断することができます。カーソルよりも大きいURLはポーリングで使用されません。

	@Override
			public Request poll(Task task) {
				if (!inited.get()) {
					init(task);
				} 
				// poll时候,需要文件记录cursor角标  (poll过的个数)
				fileCursorWriter.println(cursor.incrementAndGet());
				return queue.poll();
			}
			
			@Override
			protected void pushWhenNoDuplicate(Request request, Task task) {
				if (!inited.get()) {
					init(task);
				}
				queue.add(request);
				// push时候,需要文件记录url
				fileUrlWriter.println(request.getUrl());
			}
			
			push和poll添加url和cursor到队列和urls时,同步写入到文件中。
		
			private void init(Task task) {
		        this.task = task;
		        File file = new File(filePath);
		        if (!file.exists()) {
		            file.mkdirs();
		        }
		        readFile();
		        initWriter();
		        initFlushThread();
		        inited.set(true);
		        logger.info("init cache scheduler success");
		    }
		

初期化メソッドは主に、URLとリクエストの復元、ファイルファイルからのカーソル値の読み取り、およびURLファイルからのURLの読み取りに使用されます。次に、キューの回復は、カーソルより大きいURLに基​​づいて構築されたrequest(url)です。

     3、成分分析-ダウンローダー

ダウンローダーは、その後の処理のためにインターネットからページをダウンロードする責任があります。WebMagicは、使用のApacheのHttpClientをすることにより、ダウンロードツールとしてデフォルト

デフォルトのダウンロード実装はHttpClientDownloaderです。コア処理は、httpClientを介してページデータをダウンロードし、それをPageオブジェクトにアセンブルすることです。

  public Page download(Request request, Task task) {
        if (task == null || task.getSite() == null) {
            throw new NullPointerException("task or site can not be null");
        }
        CloseableHttpResponse httpResponse = null;
        CloseableHttpClient httpClient = getHttpClient(task.getSite());
        Proxy proxy = proxyProvider != null ? proxyProvider.getProxy(task) : null;
        HttpClientRequestContext requestContext = httpUriRequestConverter.convert(request, task.getSite(), proxy);
        Page page = Page.fail();
        try {
            httpResponse = httpClient.execute(requestContext.getHttpUriRequest(), requestContext.getHttpClientContext());
            page = handleResponse(request, request.getCharset() != null ? request.getCharset() : task.getSite().getCharset(), httpResponse, task);
            onSuccess(request);
            logger.info("downloading page success {}", request.getUrl());
            return page;
        } catch (IOException e) {
            logger.warn("download page {} error", request.getUrl(), e);
            onError(request); 
            return page;
        } finally {
            if (httpResponse != null) {
                //ensure the connection is released back to pool
                EntityUtils.consumeQuietly(httpResponse.getEntity());
            }
            if (proxyProvider != null && proxy != null) {
                proxyProvider.returnProxy(proxy, page, task);
            }
        }
    }

     第四に、成分分析-パイプライン

    Pipelineは、計算、ファイル、データベースなどへの永続性など、抽出結果の処理を担当します。デフォルトでは、WebMagicは「コンソールへの出力」と「ファイルへの保存」の2つの結果処理ソリューションを提供します。
   1.コンソールパイプライン

public class ConsolePipeline implements Pipeline {
	
	    @Override
	    public void process(ResultItems resultItems, Task task) {
	        System.out.println("get page: " + resultItems.getRequest().getUrl());
	        for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {
	            System.out.println(entry.getKey() + ":\t" + entry.getValue());
	        }
	    }
	}

 2.ファイルパイプライン

 public class FilePipeline extends FilePersistentBase implements Pipeline {

    public FilePipeline() {
        setPath("/data/webmagic/");
    }

    public FilePipeline(String path) {
        setPath(path);
    }

    @Override
    public void process(ResultItems resultItems, Task task) {
        String path = this.path + PATH_SEPERATOR + task.getUUID() + PATH_SEPERATOR;
        try {
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(getFile(path + DigestUtils.md5Hex(resultItems.getRequest().getUrl()) + ".html")),"UTF-8"));
            printWriter.println("url:\t" + resultItems.getRequest().getUrl());
            for (Map.Entry<String, Object> entry : resultItems.getAll().entrySet()) {
                if (entry.getValue() instanceof Iterable) {
                    Iterable value = (Iterable) entry.getValue();
                    printWriter.println(entry.getKey() + ":");
                    for (Object o : value) {
                        printWriter.println(o);
                    }
                } else {
                    printWriter.println(entry.getKey() + ":\t" + entry.getValue());
                }
            }
            printWriter.close();
        } catch (IOException e) {
            logger.warn("write file error", e);
        }
    }
}

 

webmagic解析作業の2番目の部分

      Webmagicページの抽出と分析の作業、webmagic分析ページの要素は、xpath、css、json、regularを介して行うことができます。
      抽出と分析の作業は、主に2つのトップレベルのインターフェイスによって実装されます。
        選択可能:ページ要素のコンテンツをラップするチェーン解析操作の最上位インターフェース。(一部のAPIは、異なる構文解析メソッドを実装するために異なるセレクターを呼び出します)
        セレクター:パーサーのトップレベルインターフェイス

1.実際の操作のセレクターインターフェイスの分析から始めましょう。

トップレベルインターフェイスには、いくつかの一般的な実装クラスがあります。セレクター 
        BaseElementSelector:jsoup、jsoup.parse(text)によって実装される基本クラス、および
                サブクラスによって実装されるテンプレートメソッドCssSelector
                cssセレクターの実装LinksSelector:実装CSSセレクタリンク抽出実装原理element.select( '')element.attr( "ABS:HREF")またはelement.attr( "HREF")
                XpathSelector:Xsoup(拡張jsoup)に基づいて、XPathのセレクタの実装
        JsonPathSelector。 jsonPathに基づくjson解析の実装

1、顶级接口Selector
		public interface Selector {
		
			// 提取一个结果
		    public String select(String text);
		
		     // 提取所有的结果
		    public List<String> selectList(String text);
		
		}
2、BaseElementSelector基类
		
		扩展实现了ElementSelector接口,根据返回值是是Element和结果值是String,以及结果List还是single一共有4个未实现
		的抽象方法,并且有四个已经实现的方法(通过Jsoup.parse(text) 将参数变成Element)
		
		 public abstract Element selectElement(Element element);

   		 public abstract List<Element> selectElements(Element element);
		
		 public String select(Element element);

   		 public List<String> selectList(Element element);
   		 

 

 
   		 3、CssSelector类
   		 
   		 元素返回值的实现很简单,就是调用Jsoup本身的节点api操作。
   		 
   		    @Override
		    public Element selectElement(Element element) {
		        Elements elements = element.select(selectorText);
		        if (CollectionUtils.isNotEmpty(elements)) {
		            return elements.get(0);
		        }
		        return null;
		    }
		
		    @Override
		    public List<Element> selectElements(Element element) {
		        return element.select(selectorText);
		    }
		   		 
	   	String返回值的实现,就要提取元素中的text字符串
	   	
	   	public List<String> selectList(Element doc) {
	        List<String> strings = new ArrayList<String>();
	        List<Element> elements = selectElements(doc);
	        if (CollectionUtils.isNotEmpty(elements)) {
	            for (Element element : elements) {
	                String value = getValue(element); // getValue获取每个元素的值,默认是提取outerHtml
	                if (value != null) {
	                    strings.add(value);
	                }
	            }
	        }
	        return strings;
	    }

4.XpathSelectorクラス--- xpath解析は、
        
            主にXsoupに基づいてセレクターを呼び出すことです。Xsoupはcssセレクターを拡張し、xpathルールに従って選択します。この拡張機能は個別に分析されます。

 

5、RegexSelector类 --- regex正则解析
			
		  private void compileRegex(String regexStr) {
		    // 大小写不分,点可以代表全部(默认不匹配换行)
            this.regex = Pattern.compile(regexStr, Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
            this.regexStr = regexStr;
		  }
		
		 // 构造方法
		  public RegexSelector(String regexStr) {
	         this.compileRegex(regexStr);
	         if (regex.matcher("").groupCount() == 0) {
	            this.group = 0; // 没有捕捉组是全部
	         }  else {
	            this.group = 1; // 有捕捉组是第一个捕捉组内容
	         }
		  }
					
		// select实现
	    @Override
	    public String select(String text) {
	        return selectGroup(text).get(group);
	    }
		
		// selectGroup所有的
		public RegexResult selectGroup(String text) {
	        Matcher matcher = regex.matcher(text);
	        if (matcher.find()) {
	            String[] groups = new String[matcher.groupCount() + 1]; // +1是因为全部结果计算为0.
	            for (int i = 0; i < groups.length; i++) {
	                groups[i] = matcher.group(i);
	            }
	            return new RegexResult(groups);
	        }
	        return RegexResult.EMPTY_RESULT;
	    }

        次に、選択可能なユニバーサルパッケージページ要素であり、APIインターフェイスのさまざまな分析方法を提供します。つまり、さまざまなセレクターを呼び出すことでさまざまな機能を実現できます。

1. Selectableトップレベルインターフェイスの
        
        主な実装:AbstractSelectable
            HtmlNode:HTML要素PlainText
            :プレーンテキスト

public interface Selectable {

		    public Selectable xpath(String xpath);
		
		    public Selectable $(String selector);
		
		    public Selectable $(String selector, String attrName);
		
		    public Selectable css(String selector);
		
		    public Selectable css(String selector, String attrName);
		
		    public Selectable smartContent();
		
		    public Selectable links();
		
		    public Selectable regex(String regex);
		
		    public Selectable regex(String regex, int group);
		
		    public Selectable replace(String regex, String replacement);
		
		    public String toString();
		    
		    public String get();
		
		    public boolean match();
		
		    public List<String> all();
		
		    public Selectable jsonPath(String jsonPath);
		
		    public Selectable select(Selector selector);
		
		    public Selectable selectList(Selector selector);
		
		    public List<Selectable> nodes();
		}

2.関数の実現。多くのインターフェース方法があるため、分析に一般的に使用されるものをいくつか選択します。

1) css方法 ($()方法) 
			 由HtmlNode实现,HtmlNode的属性有List<Element>,在创建节点时就会进行赋值。
			  public Selectable $(String selector, String attrName) {
		        CssSelector cssSelector = Selectors.$(selector, attrName); // 创建了一个css选择器
		        return selectElements(cssSelector); // 具体解析方法
		    }
		
			 protected Selectable selectElements(BaseElementSelector elementSelector) {
		        ListIterator<Element> elementIterator = getElements().listIterator();
		        if (!elementSelector.hasAttribute()) { // 无设置属性
		            List<Element> resultElements = new ArrayList<Element>();
		            while (elementIterator.hasNext()) { 
		                Element element = checkElementAndConvert(elementIterator); // 设置第一个节点为Document节点
		                List<Element> selectElements = elementSelector.selectElements(element);
		                resultElements.addAll(selectElements);
		            }
		            return new HtmlNode(resultElements); // 默认htmlNode最后返回也是包装成htmlNode
		        } else { // 有设置属性
		            // has attribute, consider as plaintext
		            List<String> resultStrings = new ArrayList<String>();
		            while (elementIterator.hasNext()) {
		                Element element = checkElementAndConvert(elementIterator);
		                List<String> selectList = elementSelector.selectList(element);
		                resultStrings.addAll(selectList);
		            }
		            return new PlainText(resultStrings); // 包装成纯文本节点返回
		        }
		    }
					
 2)、regex 正则实现
		 
		  public Selectable regex(String regex, int group) {
	        RegexSelector regexSelector = Selectors.regex(regex, group); // 正则选择器
	        return selectList(regexSelector, getSourceTexts()); // getSourceTexts获取文本--由子类实现
	    }
	    
	     protected Selectable selectList(Selector selector, List<String> strings) {
	        List<String> results = new ArrayList<String>();
	        for (String string : strings) {
	            List<String> result = selector.selectList(string);
	            results.addAll(result);
	        }
	        return new PlainText(results);
	    }
			
		//getSourceTexts的子类实现---HtmlNode
		
		protected List<String> getSourceTexts() {
	        List<String> sourceTexts = new ArrayList<String>(getElements().size());
	        for (Element element : getElements()) { // 所有的element进行toString操作。
	            sourceTexts.add(element.toString());
	        }
	        return sourceTexts;
	    }
		
	   //getSourceTexts的子类实现---PlainText

		 protected List<String> getSourceTexts() {
        return sourceTexts;
    	}


3.選択可能なものの使用

3、selectable的使用
    	
    	这些不同的selectable都挂载在Page对象 的属性下。
    	
	    private Html html;
	    private Json json;
	    private String rawText;
	    private Selectable url;
	    	
    	public Html getHtml() {
	        if (html == null) {
	            html = new Html(rawText, request.getUrl());
	        }
	        return html;
	    }
	    	
	    public Json getJson() {
	        if (json == null) {
	            json = new Json(rawText);
	        }
	        return json;
	    }
	    
	    // 下载阶段设置的url属性包装的Selectable
	    page.setUrl(new PlainText(request.getUrl()));
	    
	    // 可自由使用的rawText
	    page.setRawText(new String(bytes, charset));	

   第2部の要約:    webmagicは、解析対象の要素を選択可能な一般的なインターフェイスにパッケージ化し、解析のさまざまな操作をセレクターにパッケージ化する方法について説明します。選択可能な継承チェーンは、主にテキストノードとhtmlノードに分けられます。インターフェイスのapiメソッドオブジェクト、およびテキストノードのhtmlノードには、ユニバーサルに操作できない部分がある場合があります。このメソッドは実装をサポートしていません。実装できるメソッドは、さまざまなセレクターを呼び出すことによって実装されます。

 

終わり!

 

 

 

 

 

 

 

 

 

 

おすすめ

転載: blog.csdn.net/shuixiou1/article/details/114076352