整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

爬虫系列(1):抓取网页URL

上一节(爬虫系列(0):项目搭建)

网络爬虫的都是通过多线程,多任务逻辑实现的,在springboot框架中已封装线程池(ThreadPoolTaskExecutor),我们只需要使用就是了。

这一节我们主要实现多线程抓取网页连接信息,并将信息存储在队列里面。

引入新包

在pom中引入新包,具体如下:

 <dependency>
        <!-- common工具包 -->
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
	</dependency>
	<dependency>
	    <!-- java处理HTML的工具包 -->
		<groupId>org.jsoup</groupId>
		<artifactId>jsoup</artifactId>
		<version>1.8.3</version>
	</dependency>
	<dependency>
	    <!-- lombok工具包,简化编码 -->
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<scope>provided</scope>
	</dependency>

为了简化编码,这里引入了lombok,在使用时候IDE需要安装lombok插件,否则会提示编译错误。

配置管理

springboot的配置文件都是在application.properties(.yml)统一管理的,在这里,我们也把爬虫相关的配置通过@ConfigurationProperties注解来实现。直接上代码:

package mobi.huanyuan.spider.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 爬虫配置.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 11:10
 */
@Data
@ConfigurationProperties(prefix = "huanyuan.spider")
public class SpiderConfig {
    /**
     * 爬取页面最大深度
     */
    public int maxDepth = 2;
    
    /**
     * 下载页面线程数
     */
    public int minerHtmlThreadNum = 2;

    //=================================================
    //  线程池配置
    //=================================================
    /**
     * 核心线程池大小
     */
    private int corePoolSize = 4;

    /**
     * 最大可创建的线程数
     */
    private int maxPoolSize = 100;

    /**
     * 队列最大长度
      */
    private int queueCapacity = 1000;

    /**
     * 线程池维护线程所允许的空闲时间
     */
    private int keepAliveSeconds = 300;
}

然后,需要修改这些配置,只需要修改application.properties(.yml)里边即可:


幻猿简易爬虫配置


线程池

线程池使用springboot已有的,配置也在上边配置管理里边有,这里只初始化配置即可:

package mobi.huanyuan.spider.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 11:35
 */
@Configuration
public class ThreadPoolConfig {
    @Autowired
    private SpiderConfig spiderConfig;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(spiderConfig.getMaxPoolSize());
        executor.setCorePoolSize(spiderConfig.getCorePoolSize());
        executor.setQueueCapacity(spiderConfig.getQueueCapacity());
        executor.setKeepAliveSeconds(spiderConfig.getKeepAliveSeconds());
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

}

队列管理

这一节我们主要是抓取URL并保存进队列,所以涉及到的队列有待抓取队列和待分析队列(下一节分析时候用,这里只做存储),此外,为了防止重复抓取同一个URL,这里还需要加一个Set集合,将已访问过的地址做个记录。

package mobi.huanyuan.spider;

import lombok.Getter;
import mobi.huanyuan.spider.bean.SpiderHtml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

/**
 * 爬虫访问队列.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 10:54
 */
public class SpiderQueue {
    private static Logger logger = LoggerFactory.getLogger(SpiderQueue.class);

    /**
     * Set集合 保证每一个URL只访问一次
     */
    private static volatile Set<String> urlSet = new HashSet<>();
    /**
     * 待访问队列<br>
     * 爬取页面线程从这里取数据
     */
    private static volatile Queue<SpiderHtml> unVisited = new LinkedList<>();

    /**
     * 等待提取URL的分析页面队列<br>
     * 解析页面线程从这里取数据
     */
    private static volatile Queue<SpiderHtml> waitingMine = new LinkedList<>();

    /**
     * 添加到URL队列
     *
     * @param url
     */
    public synchronized static void addUrlSet(String url) {
        urlSet.add(url);
    }

    /**
     * 获得URL队列大小
     *
     * @return
     */
    public static int getUrlSetSize() {
        return urlSet.size();
    }

    /**
     * 添加到待访问队列,每个URL只访问一次
     *
     * @param spiderHtml
     */
    public synchronized static void addUnVisited(SpiderHtml spiderHtml) {
        if (null != spiderHtml && !urlSet.contains(spiderHtml.getUrl())) {
            logger.info("添加到待访问队列[{}] 当前第[{}]层 当前线程[{}]", spiderHtml.getUrl(), spiderHtml.getDepth(), Thread.currentThread().getName());
            unVisited.add(spiderHtml);
        }
    }

    /**
     * 待访问出队列
     *
     * @return
     */
    public synchronized static SpiderHtml unVisitedPoll() {
        return unVisited.poll();
    }

    /**
     * 添加到等待提取URL的分析页面队列
     *
     * @param html
     */
    public synchronized static void addWaitingMine(SpiderHtml html) {
        waitingMine.add(html);
    }

    /**
     * 等待提取URL的分析页面出队列
     *
     * @return
     */
    public synchronized static SpiderHtml waitingMinePoll() {
        return waitingMine.poll();
    }

    /**
     * 等待提取URL的分析页面队列大小
     * @return
     */
    public static int waitingMineSize() {
        return waitingMine.size();
    }
}

抓取任务

直接上代码:

package mobi.huanyuan.spider.runable;

import mobi.huanyuan.spider.SpiderQueue;
import mobi.huanyuan.spider.bean.SpiderHtml;
import mobi.huanyuan.spider.config.SpiderConfig;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 抓取页面任务.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 11:43
 */
public class SpiderHtmlRunnable implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(SpiderHtmlRunnable.class);
    private static boolean done = false;
    private SpiderConfig config;

    public SpiderHtmlRunnable(SpiderConfig config) {
        this.config = config;
    }

    @Override
    public void run() {
        while (!SpiderHtmlRunnable.done) {
            done = true;
            minerHtml();
            done = false;
        }
    }

    public synchronized void minerHtml() {
        SpiderHtml minerUrl = SpiderQueue.unVisitedPoll(); // 待访问出队列。
        try {
            //判断当前页面爬取深度
            if (null == minerUrl || StringUtils.isBlank(minerUrl.getUrl()) || minerUrl.getDepth() > config.getMaxDepth()) {
                return;
            }
            //判断爬取页面URL是否包含http
            if (!minerUrl.getUrl().startsWith("http")) {
                logger.info("当前爬取URL[{}]没有http", minerUrl.getUrl());
                return;
            }
            logger.info("当前爬取页面[{}]爬取深度[{}] 当前线程 [{}]", minerUrl.getUrl(), minerUrl.getDepth(), Thread.currentThread().getName());
            Connection conn = Jsoup.connect(minerUrl.getUrl());
            conn.header("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13");//配置模拟浏览器
            Document doc = conn.get();
            String page = doc.html();

            SpiderHtml spiderHtml = new SpiderHtml();
            spiderHtml.setUrl(minerUrl.getUrl());
            spiderHtml.setHtml(page);
            spiderHtml.setDepth(minerUrl.getDepth());

            System.out.println(spiderHtml.getUrl());
            // TODO: 添加到继续爬取队列
            SpiderQueue.addWaitingMine(spiderHtml);
        } catch (Exception e) {
            logger.info("爬取页面失败 URL [{}]", minerUrl.getUrl());
            logger.info("Error info [{}]", e.getMessage());
        }

    }
}

这里就是个Runnable任务,主要目标就是拉去URL数据,然后封装成SpiderHtml对象存放在待分析队列里边。 这里用到了jsoup--一个java对HTML分析操作的工具包,不清楚的可以去搜索看看,之后章节涉及到分析的部分也会用到。

其他

页面信息封装类SpiderHtml

package mobi.huanyuan.spider.bean;

import lombok.Data;

import java.io.Serializable;

/**
 * 页面信息类.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 11:02
 */
@Data
public class SpiderHtml implements Serializable {
    /**
     * 页面URL
     */
    private String url;
    /**
     * 页面信息
     */
    private String html;
    /**
     * 爬取深度
     */
    private int depth;
}

爬虫主类

package mobi.huanyuan.spider;

import mobi.huanyuan.spider.bean.SpiderHtml;
import mobi.huanyuan.spider.config.SpiderConfig;
import mobi.huanyuan.spider.runable.SpiderHtmlRunnable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 爬虫.
 *
 * @author Jonathan L.(xingbing.lai@gmail.com)
 * @version 1.0.0 -- Datetime: 2020/2/18 11:23
 */
@Component
public class Spider {
    private static Logger logger = LoggerFactory.getLogger(Spider.class);

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private SpiderConfig spiderConfig;

    public void start(SpiderHtml spiderHtml) {

        //程序启动,将第一个起始页面放入待访问队列。
        SpiderQueue.addUnVisited(spiderHtml);
        //将URL 添加到URL队列 保证每个URL只访问一次
        SpiderQueue.addUrlSet(spiderHtml.getUrl());

        //download
        for (int i = 0; i < spiderConfig.getMinerHtmlThreadNum(); i++) {
            SpiderHtmlRunnable minerHtml = new SpiderHtmlRunnable(spiderConfig);
            threadPoolTaskExecutor.execute(minerHtml);
        }
        // TODO: 监控爬取完毕之后停线程池,关闭程序
        try {
            TimeUnit.SECONDS.sleep(20);
            logger.info("待分析URL队列大小: {}", SpiderQueue.waitingMineSize());
            // 关闭线程池
            threadPoolTaskExecutor.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在"// TODO:"之后的代码逻辑这里是临时的,等后边章节完善之后,这里就慢慢去掉。

最后

要跑起这一节的代码,需要在springboot项目main方法中加入如下代码:

ConfigurableApplicationContext context = SpringApplication.run(SpiderApplication.class, args);
Spider spider = context.getBean(Spider.class);
SpiderHtml startPage = new SpiderHtml();
startPage.setUrl("$URL");
startPage.setDepth(2);
spider.start(startPage);

$URL就是需要抓取的网页地址。

springboot项目启动后,停止需要手动停止,目前没有处理抓取完自动停止运行的逻辑。 运行结果如下图:


幻猿简易爬虫运行结果


最后,这个章节完成之后整个项目的结构如下图:


幻猿简易爬虫项目结构


关于我


程序界的老猿,自媒体界的新宠 じ☆ve


程序界的老猿,自媒体界的新宠 じ☆ve

联系方式:1405368512@qq.com

在许多应用中,需要从URL中提取域名信息,以便进一步分析或处理。Python提供了强大的工具来执行这项任务。在本教程中,我们将学习如何使用Python从URL中提取域名,并提供示例代码以帮助大家入门。

URL结构分析

通常一个URL分为以下几个部分,它们是:

  • scheme ,指定我们可以用来获取在线资源的协议,例如,HTTP/HTTPS
  • netloc , net 表示网络,loc 表示位置;所以它表示URLs的网络位置
  • path ,一个网络浏览器用来访问所提供的资源的特定途径
  • params , 这些是path 元素的参数

使用urllib库解析URL

Python的标准库中有urllib模块,它包含了处理URL的各种功能。我们可以使用urllib.parse来解析URL并提取域名部分。下面是如何使用它的示例代码:

pythonfrom urllib.parse import urlparse

# 输入URL
url = "https://www.example.com/path/page.html"

# 解析URL
parsed_url = urlparse(url)

# 提取域名
domain = parsed_url.netloc

print("提取的域名是:", domain)

在这个示例中,我们首先导入了urllib.parse模块,然后定义了一个URL字符串,接着使用urlparse函数来解析URL。最后,我们从解析结果中提取域名部分并打印出来。

使用第三方库tldextract

除了标准库中的urllib,还可以使用第三方库tldextract来更方便地提取域名。tldextract库可以提取顶级域名(TLD)、域名和子域名,而且不需要对URL进行手动解析。安装命令如下:

pythonpip install tldextract

提取url代码:

pythonimport tldextract

# 输入URL
url = "https://www.example.com/path/page.html"

# 提取域名
extractor = tldextract.TLDExtract()
domain_info = extractor(url)

domain = f"{domain_info.domain}.{domain_info.suffix}"

print("提取的域名是:", domain)

在这个示例中,我们导入了tldextract库,定义了一个URL字符串,然后使用tldextract.TLDExtract()创建了一个域名提取器。最后,我们使用提取器来提取域名部分,并将其打印出来。

使用第三方库tldextract通常更方便,因为它可以处理各种URL格式,并且提供了更详细的域名信息。

总结

本文主要介绍了如何使用Python从URL中提取域名,提取域名完成后,我们就可以将域名使用到我们需要的地方了。


霍格沃兹测试开发学社|免费学习资料大放送,助你事半功倍! - 公众号 - 测试人社区

天碰到要在一个页面获取另外一个页面url传过来的参数,一开始很本能的想到了用 split(“?”)这样一步步的分解出需要的参数。

喜欢的朋友可以测试下,希望对大家有所帮助!


js方法一:正则分析法,指定参数名获取值。

function getQueryString(name){

var reg =new RegExp('(^|&)'+name+'=([^&]*)(&|$)','i');

var r = window.location.search.substr(1).match(reg);

if(r !=null){

return unescape(r[2]);

}

return null;

}

// 这样调用:

// http://orzhtml.github.io?a=1&b=2&c=3

console.log(getQueryString("a"));

console.log(getQueryString("b"));

console.log(getQueryString("c"));

结果截图:

下面举一个例子:

若地址栏URL为:abc.html?id=123&url=http://orzhtml.github.io

那么,但你用上面的方法去调用:alert(getQueryString("url"));

则会弹出一个对话框:内容就是 http://orzhtml.github.io

如果用:alert(getQueryString("id"));那么弹出的内容就是 123 啦;

当然如果你没有传参数的话,比如你的地址是 abc.html 后面没有参数,那强行输出调用结果有的时候会报错:

所以我们要加一个判断 ,判断我们请求的参数是否为空,首先把值赋给一个变量:

var myurl= getQueryString("url");

if(myurl != null && myurl.toString().length>1) {

alert(myurl);

}


js方法二:获取所有参数这样就不会报错了,结果返回始终会是一个对象!

function GetRequest(){

var url = location.search;//获取url中"?"符后的字串

var theRequest ={};

if(url.indexOf("?")!=-1){

var str = url.substr(1);

strs = str.split("&");

for(var i =0; i < strs.length; i ++){

theRequest[strs[i].split("=")[0]]= unescape(strs[i].split("=")[1]);

}

}

return theRequest;

}

// 这样调用

// http://orzhtml.github.io?a=4&b=5&c=6

var Request = {};

Request = GetRequest();

console.log(Request);

console.log(Request['a']);

console.log(Request['b']);

console.log(Request['c']);

结果截图:


本文内容均属个人原创作品,转载此文章须附上出处及原文链接。

加关注,定时推送,互动精彩多,若你有更好的见解,欢迎留言探讨!