Análisis de código fuente de procesos y componentes centrales de webmagic

Descripción general : WebMagic divide la estructura Downloader, PageProcessor, Scheduler, Pipelinecuatro componentes, araña los organizan por sí. Estos cuatro componentes corresponden a las funciones de descarga, procesamiento, administración y persistencia en el ciclo de vida del rastreador.

La clase de inicio de Webmagic es Spider. Cuando se ejecuta el método run (), varios componentes se inicializarán y ejecutarán en un bucle. Las solicitudes se toman del planificador, se empaquetan en ejecutable y se procesan. En este proceso, el componente Scheduler es responsable de la deduplicación y la carga de solicitudes pendientes, el componente Downloader es responsable de descargar los resultados de la solicitud y empaquetarlos en clases de página, y el componente pageProcessor es responsable de procesar los resultados de la página y puede agregar URL adicionales que deben recopilarse de la página e instalarlo con page.resultItems Los datos del resultado procesado se envían al componente Pipeline para procesar el resultado.

Para un uso específico, consulte el documento oficial http://webmagic.io/docs/zh/ , o busque casos relacionados Este artículo analiza principalmente el código fuente de webmagic.

    1. Análisis de procesos

1. Métodos principales    

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());
    }

      Como se puede ver en lo anterior, Spider es coordinar la ejecución de varios componentes. La lógica central es tomar la solicitud del programador y empaquetarla en una tarea para ser ejecutada por el grupo de subprocesos. Varios componentes principales son responsables de su responsabilidades respectivas en este proceso.

   2) Inicializar el componente 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();
    }

 La implementación inicial de varios componentes principales downloader-HttpClientDownloader, pipelines-ConsolePipeline, planificador-QueueScheduler pageProcessor-formulado manualmente.

 3. Procesamiento del método de solicitud processRequest

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

 // Descarga procesada con éxito

    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;
    }

  La lógica de procesamiento para una descarga exitosa es procesar la página a través del pageProcessor, luego extraer addTragetRequest y agregarlo al programador, y luego determinar si isSkip está configurado, si no, usar múltiples canalizaciones para procesar los resultados.

/// El manejo de fallas de descarga

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

// reintento de bucle

    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());
    }

 

 

Para el proceso anterior, se dibujan dos diagramas de flujo más detallados que la versión oficial.

 

Programador de análisis de dos componentes (programación)

Scheduler Scheduler es responsable de administrar y programar las URL que se rastrearán, así como de algunos trabajos de deduplicación. WebMagic proporciona una cola de memoria LinkedBlockingQueue de forma predeterminada para administrar URL y utiliza colecciones para eliminar duplicados. También admite el uso de Redis para la gestión distribuida. Las principales implementaciones del planificador son las siguientes.

DuplicateRemovedScheduler : clase base Scheduler, un DuplicatedRemover incorporado se implementa como HashSetDuplicateRemover.
QueueScheduler : Queue Scheduler, que almacena las conexiones que se recopilarán a través de LinkedBlockingQueue.
FileCacheQueueScheduler : Programador de cola de caché de archivos. Sobre la base de la cola, todas las direcciones URL y ubicaciones de sondeo se registran a través de archivos. Si se detiene temporalmente, la recopilación se puede reanudar a través de archivos.
RedisScheduler : use Redis para guardar la cola de rastreo, donde la lista guarda la cola de la cola y el conjunto es responsable de la deduplicación, el hash y el almacenamiento de los datos adicionales solicitados.

2.1 Programador duplicado eliminado

   	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.2 Programación de URL de cola de memoria QueueScheduler

La implementación de QueueScheduler es que un LinkedBlockingQueue interno es responsable de obtener o sondear para obtener la URL.

2.3 Programación de URL distribuidas de RedisScheduler Redis

1) Variables de miembro

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();
	}

Lo anterior es la variable miembro, la cola de URL se guarda a través de la cola, el conjunto es responsable de la deduplicación y el hash es responsable de almacenar los datos auxiliares.

Además, el constructor pasó o construyó jedisPool y realizó DuplicateRemover en sí mismo, y lo configuró en el campo duplicateRemover de este objeto.

2) Implementación de desduplicación

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) empujar 

      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) piscina

  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 es un programador de cola de caché de archivos. Utiliza principalmente 2 archivos para registrar todas las URL y la cantidad de cursores que se han utilizado . El método init se inicializa cuando se ejecuta la inserción o la encuesta y se leen 2 archivos durante la inicialización. Todas las URL almacenados en el archivo se agregan a las URL, y las URL más grandes que el número de cursores se empaquetan como una solicitud y se agregan a la cola.

 Y FileCacheQueueScheduler tiene dos printWriter creados cuando se inicializa, responsables de escribir la url agregada a este escritor al mismo tiempo al presionar (incremental), y al sondear, el número de sondeos se registra a través del cursor, de modo que al recuperarlo, puede ser a través del cursor A juzgar, la URL más grande que el cursor no es utilizada por la encuesta.

	@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");
		    }
		

El método de inicialización se utiliza principalmente para restaurar URL y solicitudes, leer el valor del cursor del archivo y leer las URL del archivo URL. Entonces, la recuperación de la cola es la solicitud (url) construida en base a la URL más grande que el cursor.

     Descargador de análisis de tres componentes

Downloader es responsable de descargar páginas de Internet para su posterior procesamiento. WebMagic utiliza Apache HttpClient como herramienta de descarga de forma predeterminada .

La implementación de descarga predeterminada es HttpClientDownloader. El procesamiento principal es descargar datos de la página a través de httpClient y ensamblarlos en un objeto 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);
            }
        }
    }

     Cuarto, análisis de componentes: canalización

    Pipeline es responsable del procesamiento de los resultados de la extracción, incluidos los cálculos, la persistencia en archivos, bases de datos, etc. De forma predeterminada, WebMagic proporciona dos soluciones de procesamiento de resultados: "enviar a la consola" y "guardar en el archivo".
   1. Canalización de la consola

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. Canalización de archivos

 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);
        }
    }
}

 

La segunda parte del trabajo de análisis de webmagic

      Trabajo de análisis y extracción de páginas webmagic, los elementos de la página de análisis webmagic pueden ser a través de xpath, css, json, regular.
      El trabajo de extracción y análisis se implementa principalmente mediante dos interfaces de nivel superior.
        Seleccionable : una interfaz de nivel superior para operaciones de análisis en cadena que envuelve el contenido de los elementos de la página. (Algunas de las API llamarán a diferentes selectores para implementar diferentes métodos de análisis)
        Selector : la interfaz de nivel superior del analizador

1. Comencemos analizando la interfaz del selector de operación real.

Hay varias clases de implementación comunes para el selector de interfaz de nivel superior  :
        BaseElementSelector : es una clase base implementada por jsoup, un jsoup.parse (texto) y un método de plantilla
                CssSelector implementado por subclases : la implementación del selector
                css LinksSelector: el implementación del selector css Principio de implementación de extracción de enlaces element.select ('a') element.attr ("abs: href") o element.attr ("href")
                XpathSelector : Implementación del selector xpath basado en Xsoup (jsoup extendido)
        JsonPathSelector : Basado en jsonPath Una implementación de análisis json de

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. Clase XpathSelector --- el análisis de xpath es
        
            principalmente para llamar al selector basado en Xsoup que extiende el selector css y selecciona de acuerdo con las reglas de xpath Esta extensión se analizará por separado.

 

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;
	    }

        En segundo lugar, elementos de página de empaquetado universal seleccionables y proporciona una variedad de métodos analíticos de interfaz api, es decir, se pueden lograr diferentes funciones llamando a diferentes selectores.

1. La
        
        implementación principal de la interfaz de nivel superior seleccionable : AbstractSelectable
            HtmlNode: elemento HTML
            PlainText: texto sin formato

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. Realización de funciones, debido a que existen muchos métodos de interfaz, seleccione algunos de uso común para el análisis.

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. Uso de seleccionables

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));	

   La segunda parte resume:    webmagic habla sobre empaquetar los elementos que se analizarán en una interfaz general seleccionable y empaquetar varias operaciones de análisis sintáctico en selectores. La cadena de herencia de seleccionables se divide principalmente en nodos de texto y nodos html. Los métodos api de la interfaz todos devolverán seleccionable. Object, y el nodo de texto html node puede tener algunas partes que no pueden ser operadas universalmente. El método no admite implementación. El método que puede implementarse se implementa llamando a diferentes selectores.

 

¡fin!

 

 

 

 

 

 

 

 

 

 

Supongo que te gusta

Origin blog.csdn.net/shuixiou1/article/details/114076352
Recomendado
Clasificación