WebMagic抓取医院科室,医生信息实战及踩坑

news2024/12/23 1:31:00

简介

WebMagic项目代码分为核心和扩展两部分。核心部分(webmagic-core)是一个精简的、模块化的爬虫实现,而扩展部分则包括一些便利的、实用性的功能。WebMagic的架构设计参照了Scrapy,目标是尽量的模块化,并体现爬虫的功能特点。
WebMagic概览

使用场景

我用WebMagic抓取公立医院的科室,科室详情,医生,医生详情信息,爬虫技术玩玩还可以,实际应用需谨慎。

实战

先放一篇参考博客WebMagic,我主要参考这篇博客进行的开发,在此基础上进行的修改,并且踩了不少坑,记录一下

集成

首先是pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>WebMagicDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>WebMagicDemo</name>
    <description>WebMagicDemo</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--WebMagic-->
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.8.0</version>

        </dependency>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--测试组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.15.4</version>
        </dependency>

        <!--selenium依赖-->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.8.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>guava</artifactId>
                    <groupId>com.google.guava</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>okio</artifactId>
                    <groupId>com.squareup.okio</groupId>
                </exclusion>
            </exclusions>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


其中easyexcel可选,不需要生成excel不需要集成

JdChromeDownloader

@Component
public class JdChromeDownloader implements Downloader
{

    //声明驱动
    private RemoteWebDriver driver;

    public JdChromeDownloader() {
        //第一个参数是使用哪种浏览器驱动
        //第二个参数是浏览器驱动的地址
        System.setProperty("webdriver.chrome.driver","C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe");

        //创建浏览器参数对象
        ChromeOptions chromeOptions = new ChromeOptions();

        // 设置为 headless 模式,上课演示,或者学习不要打开
        // chromeOptions.addArguments("--headless");
        // 设置浏览器窗口打开大小
        chromeOptions.addArguments("--window-size=1280,700");
//设置自动化启动时,不显示正在受到自动化软件控制的提示栏
        chromeOptions.setExperimentalOption("excludeSwitches", new String[] {"enable-automation","load-extension"});
        //针对反爬机制,把浏览器不标记为webdriver启动的浏览器。
        chromeOptions.addArguments("--disable-blink-features=AutomationControlled");
        chromeOptions.addArguments("no-sandbox");
        chromeOptions.addArguments("disable-dev-shm-usage");
        chromeOptions.addArguments("--remote-allow-origins=*");

        //创建驱动
        this.driver = new ChromeDriver(chromeOptions);
    }

    @Override
    public Page download(Request request, Task task) {
        try {
            driver.get(request.getUrl());
            Thread.sleep(2000);

            //无论是搜索页还是详情页,都滚动到页面底部,所有该加载的资源都加载
            //需要滚动到页面的底部,获取完整的商品数据
            driver.executeScript("window.scrollTo(0, document.body.scrollHeight - 1000)");
            Thread.sleep(2000l);

            //获取页面对象
            Page page = createPage(request.getUrl(), driver.getPageSource());

            //判断是否是搜索页
            if (request.getUrl().contains("search")) {
                //如果请求url包含search,说明是搜索结果页
                //在搜索结果页,需要获取下一页的链接地址
                //点击下一页按钮,在下一页中获取当前页的url(就是下一页的url),放到任务队列中
                WebElement next = driver.findElement(By.cssSelector("a.pn-next"));
                //点击
                next.click();

                //获取当前页面(其实就是下一页)的url地址
                String nextUrl = driver.getCurrentUrl();

                //使用page对象,把下一页url放到任务列表中
                page.addTargetRequest(nextUrl);
            }

            //关闭浏览器
            //driver.close();

            return page;

        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        return null;
    }

    @Override
    public void setThread(int threadNum) {

    }

    //构建page返回对象
    private Page createPage(String url, String content) {
        Page page = new Page();
        page.setRawText(content);
        page.setUrl(new PlainText(url));
        page.setRequest(new Request(url));
        page.setDownloadSuccess(true);

        return page;
    }

}

StartCrawler

@Component
public class StartCrawler
{
    //@Resource
    //private JdChromeDownloader downloader;
    @Resource
    private MyPipeline jpaPipeline;

    @Resource
    private DeptPipeline deptPipeline;

    @Resource
    private DoctorPipeline doctorPipeline;
    //声明医院科室
    String url = "http://xxxxxx/keshi/";
    //声明医院专家
    String doctorsUrl="http://xxxxxx/zhuanjia/";
    @Scheduled(cron = "0/5 * * * * *")
    public void run() {
        Spider.create(new WxhsDoctorProcessor())
                //.addUrl("https://www.jd.com/news.html?id=38673")
                .addUrl(doctorsUrl)
                //设置下载器
               // .setDownloader(downloader)
               // .addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
                .addPipeline(doctorPipeline)
                .run();
    }


}

这个文件的作用,是5秒钟调用一次爬虫

坑1 集成完毕,运行环境,报“An attempt was made to call a method that does not exist. The attempt was made from the following location”

这个是 maven 版本冲突问题,解决的办法很简单
dea 安装 mavenhelper 插件,点开 pom.xml 文件 =》 左下角有 text 和 Dependency Analyzer , 点 Dependency Analyzer, 选中 Conflicts ,选中需要解决的冲突 ==》 鼠标右键,exclude; 如果不是根的话就,Jump left Tree 之后再 exclude,reimport 就好了
在这里插入图片描述

Selenium+headless浏览器实现动态爬虫

我们可以使用HttpClient模拟浏览器抓取静态html,但是对js的解析部分还是很薄弱。虽然我们可以读取js的运作机制并且找到相关数据,但是这样会耗费大量时间。为了解决这个问题我们可以使用工具来模拟浏览器的运行,直接获取解析结果。这就是使用Selenium+headless浏览器来实现动态爬虫。
Selenium
Selenium是一个用于Web应用程序测试的工具。Selenium可以使用代码控制浏览器,就像真正的用户在操作一样。而对于爬虫来说,使用Selenium操控浏览器来爬取网上的数据那么肯定是爬虫中的杀手武器。Selenium支持多种浏览器可以是chrome、Firefox、PhantomJS等

使用WebDriver在Chrome浏览器上进行测试时或者做页面抓取,需要从http://chromedriver.storage.googleapis.com/index.html网址中下载与本机chrome浏览器对应的驱动程序,驱动程序名为chromedriver。chromedriver的版本需要和本机的chrome浏览器对应,才能正常使用,一般情况下下载最新版就可以了。

headless浏览器是一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现

如果想要实现动态爬虫,需要安装chromedriver,可以在浏览器驱动官网网站和淘宝镜像网站下载,先查看浏览器版本,
在这里插入图片描述
然后下载对应的驱动chromedriver.exe,放到浏览器安装目录下,在Downloader文件中进行设置,然后设置Spider的下载器 .setDownloader(downloader),这样运行起来,会调起浏览器,模拟点击动作

坑2浏览器驱动相关问题

我浏览器版本是118,然后驱动最高是114,所以我只能卸载浏览器,然后重新安装的114的,但是会自动升级成最新版本,所以要关闭谷歌浏览器的自动更新功能。关闭方法在这谷歌浏览器自动更新怎么关闭
如果不想要这个功能,可以把代码中的JdChromeDownloader 的@Component去掉,然后把.setDownloader(downloader)去掉就可以了

item

科室信息

@Data
public class DeptItem
{
    private String deptType;
    private String title;
    private String url;
    private String deptDetails;

}

医生信息

@Data
public class DoctorItem
{
    private String doctorType;
    private String name;
    private String url;
    private String img;
    private HashMap<String,String> doctorDetails;

}

pipeline

@Component
public class DoctorPipeline implements Pipeline
{
    private final static Logger log = LoggerFactory.getLogger(DoctorPipeline.class);

    @Override
    public void process(ResultItems resultItems, Task task)
    {
        //获取医生数据
        List<DeptItem> itemList = resultItems.get("itemList");

           log.info("解析医生结果"+itemList);



    }
}
@Component
public class DeptPipeline implements Pipeline
{
    private final static Logger log = LoggerFactory.getLogger(DeptPipeline.class);
    String filePath=TestFileUtil.getPath();
    File templateFile = new File(filePath, "科室介绍临时表.xlsx");
    File destFile = new File(filePath, "科室介绍.xlsx");
    private List<DeptItem> results=new ArrayList();
    @Override
    public void process(ResultItems resultItems, Task task)
    {
        //获取科室数据
        List<DeptItem> itemList = resultItems.get("itemList");
        boolean deptDetail=resultItems.get("deptDetail");
       if(!deptDetail){
           //String fileName = TestFileUtil.getPath() + "科室" + System.currentTimeMillis() + ".xlsx";
           //EasyExcel.write(fileName, DeptData.class).sheet("科室").doWrite(itemList);
           log.info("解析科室结果"+itemList);
       }else {
           //先缓存到本地,实际可以写入数据库
          // results.addAll(itemList);
           log.info("解析科室介绍结果"+itemList);
       }


    }

}

重点是网页解析

通过继承PageProcessor来进行网页解析,先看一下科室信息
在这里插入图片描述
对应的样式是
在这里插入图片描述
这一段是比较好解析的,

 //科室名称
            Selectable selectable = page.getHtml()
                    .css("div.right_bottom");
            List<Selectable> nodes = selectable.css("ul.ksxhul")
                    .nodes();

            List<Selectable> deptTypes = selectable.xpath("//dl/dt/a/strong/text()")
                    .nodes();


            //判断nodes是否有值
            if (nodes != null && nodes.size() > 0)
            {
                List<DeptItem> itemList = new ArrayList<>();


                for (int i = 0; i < nodes.size(); i++)
                {

                    List<Selectable> ksList = nodes.get(i)
                            .css("li.keshili")
                            .nodes();

                    for (Selectable ks : ksList)
                    {
                        //创建对象
                        DeptItem item = new DeptItem();
                        if (i < deptTypes.size() && deptTypes.get(i) != null)
                        {
                            item.setDeptType(deptTypes.get(i)
                                    .toString());
                        }

                        item.setUrl(ks.links()
                                .toString());
                        item.setTitle(ks.$("a", "text")
                                .toString());
                        //放到集合中
                        itemList.add(item);

                        //把商品详情页的url放到url任务队列中
                        page.addTargetRequest(item.getUrl());
                    }

                }

                //把需要持久化的数据放到ResultItems中
                page.putField("itemList", itemList);
                page.putField("deptDetail", false);
            }

解析出来的结果是
在这里插入图片描述
点击科室,进入科室详情,这个比较复杂,有多种样式,粗略数了数,得十种样式,这样的话,还真不如粘贴复制来得快。我需要把页面里的内容分类
在这里插入图片描述
按照“【科室概况】”,“【科室特色】”,“【科研及教育概况】”,“【获得的荣誉称号】”等取出相应的内容,保存到数据库,这是一种样式,比较好解析。里边内容的html标签就筛选掉吧,如果再去解析,实在是太复杂了

//去除html标签
 private   String delHTMLTag(String htmlStr){
        String regEx_script="<script[^>]*?>[\\s\\S]*?<\\/script>"; //定义script的正则表达式
        String regEx_style="<style[^>]*?>[\\s\\S]*?<\\/style>"; //定义style的正则表达式
        String regEx_html="<[^>]+>"; //定义HTML标签的正则表达式

        Pattern p_script=Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE);
        Matcher m_script=p_script.matcher(htmlStr);
        htmlStr=m_script.replaceAll(""); //过滤script标签

        Pattern p_style=Pattern.compile(regEx_style,Pattern.CASE_INSENSITIVE);
        Matcher m_style=p_style.matcher(htmlStr);
        htmlStr=m_style.replaceAll(""); //过滤style标签

        Pattern p_html=Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE);
        Matcher m_html=p_html.matcher(htmlStr);
        htmlStr=m_html.replaceAll(""); //过滤html标签

        return htmlStr.trim(); //返回文本字符串
    }
Selectable trs = page.getHtml()
                    .xpath("//div[@class=contents]/table/tbody/tr");
            HashMap<String, String> deptDetail = new HashMap<>();
            if (trs != null && trs.nodes()
                    .size() > 0)
            {
                for (Selectable tr : trs.nodes())
                {
                    List<String> all = tr.xpath("//td/p/span")
                            .all();
                    Selectable key = tr.xpath("//td/p/b/span/text()");
                    if (StringUtils.isNotBlank(key.toString()))
                    {
                        String detail = String.join("", all);
                        String detailNoHtml=delHTMLTag(detail);
                        Pattern pattern = Pattern.compile("&nbsp;");
                        Matcher matcher = pattern.matcher(detailNoHtml);
                        String result = matcher.replaceAll("");
                        deptDetail.put(key.toString(), result);
                    }


                }

            }

这样解析出来的数据就是

解析科室介绍结果[DeptItem(deptType=null, title=心血管内科, url=null, deptDetails={【科研及教育概况】=获国家和省市级科研课题资助30余项,包括国家自然科学基金课题资助项目12项,其中面上项目3项,青年基金9项;江苏省自然科学基金资助项目6项。获省、市科技进步奖及医学新技术引进奖29项,其中省级科技进步奖及医学新技术引进奖18项,包括江苏省科技进步三等奖1项,江苏省医学新技术引进一等奖3项,二等奖12项,江苏医学科技奖三等奖2项。近5年来,在SCI及中华系列杂志发表论文100余篇,其中I区和II区SCI论文20余篇,在《中华心血管病杂志》上发表论文30余篇。目前有博士研究生导师1人,硕士研究生导师5人,目前已培养博士和硕士研究生60余名。, 【科室特色】=有冠心病(心内一科)、起搏电生理(心内二科)和普通心脏病(心内三科)和心脏重症监护室(CCU)四个病区,135张床位,并有独立的心功能科和心血管病实验室。设有心血管专科、专家及高级专家门诊及冠心病、起搏和电生理等专病门诊。常规开展磁导航指导下复杂心律失常射频消融术、心脏再同步起搏技术(CRT及CRT-D)、自动复律除颤起搏器植入术(ICD)、希氏束起搏术、埋藏式心脏起搏器植入术、经皮冠状动脉介入(PCI)治疗和先天性心脏病介入治疗术。近年来开展的新技术有房颤冷冻球囊消融术、无导线心脏起搏器植入术、左心耳封堵术、皮下ICD植入术、经导管主动脉瓣置换术(TAVR)。, 【科室概况】=南京医科大学附属无锡人民医院心血管内科为江苏省重点学科建设单位、江苏省首批临床重点专科、南京医科大学重点学科、南京医科大学博士研究生培养点和博士后流动站、国家卫健委首批介入准入资格学科和中国医师协会介入培训基地。拥有无锡市“太湖人才计划”顶级医学专家团队,是无锡市心血管病临床医学中心。心血管内科现有医护人员95人,其中医生41人,护士54人,其中医生高级职称29人,医学博士17人、医学硕士22人,留学归国人员6人。拥有磁导航系统、双C臂DSA、杂交手术室、双源CT、三维电生理标测系统、冠状动脉内超声和食道超声等先进设备。, 【获得的荣誉称号】=2022年获批江苏省心血管内科重点学科建设单位2022年获批房颤中心示范基地2021年获批无锡市医学重点学科2021获批无锡市“太湖人次计划”顶级医学专家团队2019年王如兴获得无锡市“五一”劳动奖章})]

现在发现还有多种样式,比如
在这里插入图片描述
这样的样式看着都头大,如果还像之前那样解析,只能说很复杂,现在按最简单的做,就是把内容全部取出来,去掉html标签,然后后期根据业务需求,比如获取“【科研及教育概况】”的数据,可以通过字符串分割来获取

坑3.如何去掉java字符串里的html标签

首先想到的是通过正则表达式来删除,比如

 private String delHTMLTag(String htmlStr)
    {
        String regEx_script = "<script[^>]*?>[\\s\\S]*?<\\/script>"; //定义script的正则表达式
        String regEx_style = "<style[^>]*?>[\\s\\S]*?<\\/style>"; //定义style的正则表达式
        String regEx_html = "<[^>]+>"; //定义HTML标签的正则表达式

        Pattern p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
        Matcher m_script = p_script.matcher(htmlStr);
        htmlStr = m_script.replaceAll(""); //过滤script标签

        Pattern p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
        Matcher m_style = p_style.matcher(htmlStr);
        htmlStr = m_style.replaceAll(""); //过滤style标签

        Pattern p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        Matcher m_html = p_html.matcher(htmlStr);
        htmlStr = m_html.replaceAll(""); //过滤html标签

        return htmlStr.trim(); //返回文本字符串
    }

然后这样使用

        String detailNoHtml = delHTMLTag(detail);
        Pattern pattern = Pattern.compile("&nbsp;");
        Matcher matcher = pattern.matcher(detailNoHtml);
        String result=matcher.replaceAll("");

结果,不理想,虽然标签去掉了,但是留下好多空白,这个时候,需要用到Jsoup

Jsoup.parse(detail).text().replace("科室专家)","");

因为我这个网页,最后都有"科室专家)”四个字,是另一个标签的,与内容无关,所以去掉
在这里插入图片描述
我把优化后的科室解析方法发一下

@Component
public class WuxiHospitalPageProcessor implements PageProcessor
{
    private final static Logger log = LoggerFactory.getLogger(WuxiHospitalPageProcessor.class);

    @Override
    public void process(Page page)
    {
        Selectable top = page.getHtml()
                .css("div.right_top", "text");

        log.info("getHtml" + top);
        //科室详情页
        if (StringUtils.isNotBlank(top.toString()) && top.toString()
                .contains("内容阅读"))
        {
            Selectable selectable = page.getHtml()
                    .css("div.right_bottom");

            Selectable title = selectable.css("div.titleks", "text");


            String result = null;

            List<String> trsOthers = page.getHtml()
                    .xpath("//div[@class=contents]")
                    .all();//内容放在pre标签的

            result = filterResult(trsOthers);


            List<DeptItem> itemList = new ArrayList<>();
            DeptItem item = new DeptItem();
            item.setDeptDetails(result);
            item.setTitle(title.toString());

            //放到集合中
            itemList.add(item);

            //把需要持久化的数据放到ResultItems中
            page.putField("itemList", itemList);
            page.putField("deptDetail", true);
        }
        else
        {
            //科室名称
            Selectable selectable = page.getHtml()
                    .css("div.right_bottom");
            List<Selectable> nodes = selectable.css("ul.ksxhul")
                    .nodes();

            List<Selectable> deptTypes = selectable.xpath("//dl/dt/a/strong/text()")
                    .nodes();


            //判断nodes是否有值
            if (nodes != null && nodes.size() > 0)
            {
                List<DeptItem> itemList = new ArrayList<>();


                for (int i = 0; i < nodes.size(); i++)
                {

                    List<Selectable> ksList = nodes.get(i)
                            .css("li.keshili")
                            .nodes();

                    for (Selectable ks : ksList)
                    {
                        //创建对象
                        DeptItem item = new DeptItem();
                        if (i < deptTypes.size() && deptTypes.get(i) != null)
                        {
                            item.setDeptType(deptTypes.get(i)
                                    .toString());
                        }

                        item.setUrl(ks.links()
                                .toString());
                        item.setTitle(ks.$("a", "text")
                                .toString());
                        //放到集合中
                        itemList.add(item);

                        //把商品详情页的url放到url任务队列中
                        page.addTargetRequest(item.getUrl());
                    }

                }

                //把需要持久化的数据放到ResultItems中
                page.putField("itemList", itemList);
                page.putField("deptDetail", false);
            }
        }


    }

    private String filterResult(List<String> all)
    {
        String detail = String.join("", all);
        //String detailNoHtml = delHTMLTag(detail);
        //Pattern pattern = Pattern.compile("&nbsp;");
        //Matcher matcher = pattern.matcher(detailNoHtml);
        //String result=matcher.replaceAll("");
        return Jsoup.parse(detail).text().replace("科室专家)","");
    }

    private String delHTMLTag(String htmlStr)
    {
        String regEx_script = "<script[^>]*?>[\\s\\S]*?<\\/script>"; //定义script的正则表达式
        String regEx_style = "<style[^>]*?>[\\s\\S]*?<\\/style>"; //定义style的正则表达式
        String regEx_html = "<[^>]+>"; //定义HTML标签的正则表达式

        Pattern p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
        Matcher m_script = p_script.matcher(htmlStr);
        htmlStr = m_script.replaceAll(""); //过滤script标签

        Pattern p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
        Matcher m_style = p_style.matcher(htmlStr);
        htmlStr = m_style.replaceAll(""); //过滤style标签

        Pattern p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
        Matcher m_html = p_html.matcher(htmlStr);
        htmlStr = m_html.replaceAll(""); //过滤html标签

        return htmlStr.trim(); //返回文本字符串
    }


    private Site site = Site.me()
            .setTimeOut(2000);

    @Override
    public Site getSite()
    {
        return site;
    }
}

医生的页面简单多了
在这里插入图片描述
在这里插入图片描述
直接贴代码了

/**
 * @author hzj 专家列表
 * @date 2023/10/16 16:21
 */
public class WxhsDoctorProcessor implements PageProcessor
{
    private final static Logger log = LoggerFactory.getLogger(WuxiHospitalPageProcessor.class);
    @Override
    public void process(Page page)
    {
        Selectable top = page.getHtml()
                .css("div.right_top", "text");

        log.info("getHtml" + top);
        //医生详情页
        if (StringUtils.isNotBlank(top.toString()) && top.toString()
                .contains("内容阅读"))
        {
            Selectable selectable = page.getHtml()
                    .css("div.right_bottom");

            Selectable title = selectable.css("div.title", "text");


            Selectable trs = page.getHtml()
                    .xpath("//div[@class=contents]/table/tbody/tr");
            HashMap<String, String> doctorDetail = new HashMap<>();
            if (trs != null && trs.nodes()
                    .size() > 0)
            {
                for (Selectable tr : trs.nodes())
                {
                    List<Selectable> doctor = tr.xpath("//td/text()").nodes().stream().filter(x->!"".equals(x.toString())).collect(Collectors.toList());
                    if(doctor.size()==2){
                        doctorDetail.put(doctor.get(0).toString(), doctor.get(1).toString());
                    }else{
                        doctorDetail.put(title.toString(), doctor.toString());
                    }
                }

            }


            List<DoctorItem> itemList = new ArrayList<>();
            DoctorItem item = new DoctorItem();
            item.setName(title.toString());
            item.setDoctorDetails(doctorDetail);


            //放到集合中
            itemList.add(item);

            //把需要持久化的数据放到ResultItems中
            page.putField("itemList", itemList);

        }
        else
        {
            //科室名称
            Selectable selectable = page.getHtml()
                    .css("div.right_bottom");
            List<Selectable> nodes = selectable.css("ul.zjxhul")
                    .nodes();

            List<Selectable> doctorTypes = selectable.xpath("//dl/dt/a/strong/text()")
                    .nodes();
            //判断nodes是否有值
            if (nodes != null && nodes.size() > 0)
            {
                List<DoctorItem> itemList = new ArrayList<>();
                for (int i = 0; i < nodes.size(); i++)
                {

                    List<Selectable> dcList = nodes.get(i)
                            .css("li.LMXHZJLB")
                            .nodes();

                    for (Selectable dc : dcList)
                    {
                        //创建对象
                        DoctorItem item = new DoctorItem();
                        if (i < doctorTypes.size() && doctorTypes.get(i) != null)
                        {
                            item.setDoctorType(doctorTypes.get(i)
                                    .toString());
                        }

                        item.setUrl(dc.links()
                                .toString());
                        item.setImg(dc.css("a > img", "src").toString());
                        item.setName(dc.$("a", "text")
                                .toString());
                        //放到集合中
                        itemList.add(item);

                        //把医生详情页的url放到url任务队列中
                        page.addTargetRequest(item.getUrl());
                    }

                }

                //把需要持久化的数据放到ResultItems中
                page.putField("itemList", itemList);

            }
        }


    }

    private Site site = Site.me()
            .setTimeOut(2000);

    @Override
    public Site getSite()
    {
        return site;
    }
}

StartCrawler里切换一下医生相关配置就好了

public void run() {
        Spider.create(new WxhsDoctorProcessor())
                //.addUrl("https://www.jd.com/news.html?id=38673")
                .addUrl(doctorsUrl)
                //设置下载器
               // .setDownloader(downloader)
               // .addPipeline(new JsonFilePipeline("D:\\webmagic\\"))
                .addPipeline(doctorPipeline)
                .run();
    }

在这里插入图片描述
医生详情
在这里插入图片描述
代码结构
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1106501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

支持PC端、手机端、数据大屏端的Spring Cloud智慧工地云平台源码

技术架构&#xff1a;微服务JavaSpring Cloud VueUniApp MySql 智慧建筑工地云平台主要利用大数据、物联网等技术&#xff0c;整合工地信息、材料信息、工程进度等&#xff0c;实现对建筑项目的全程管理。它可以实现实时监测和控制&#xff0c;有效解决施工中的问题&#xff0c…

基于springboot的网上商城设计与实现(包调试+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等&#xff0c;今天给大家介绍一篇基于springbo…

【EI会议征稿】2024年遥感技术与测量测绘国际学术会议(RSTSM 2024)

2024年遥感技术与测量测绘国际学术会议&#xff08;RSTSM 2024&#xff09; 2024 International Conference on Remote Sensing Technology and Survey Mapping 2024年遥感技术与测量测绘国际学术会议&#xff08;RSTSM 2024&#xff09;将在2024年1月12-14日于吉林长春召开。…

耐心使用FPmarkets时间框架交易法,想亏钱都难

很多投资者在交易中极易喜欢使用热门工具&#xff0c;结果不仅没有盈利还把自己的本金亏进去&#xff0c;今天FPmarkets就分享时间框架交易法&#xff0c;想亏钱都难。 FPmarkets积累了处理不同时间框架的经验&#xff0c;并了解在此基础上的工具和指标在较短的时间框架内更为敏…

java--关键字、标识符

1.关键字 1.java语言自己用到的一些词&#xff0c;有特殊作用的&#xff0c;我们称之为关键字&#xff0c;如&#xff1a;public、class、int、double... 2.注意&#xff1a;关键字是java用了的&#xff0c;我们就不能用来做为&#xff1a;类名、变量名&#xff0c;否则会报错…

web前端面试-- 手写原生Javascript方法(new、Object.create)

web面试题 本人是一个web前端开发工程师&#xff0c;主要是vue框架&#xff0c;整理了一些面试题&#xff0c;今后也会一直更新&#xff0c;有好题目的同学欢迎评论区分享 ;-&#xff09; web面试题专栏&#xff1a;点击此处 手动实现Object.create 通过Object.create&#…

双指针算法解决 移动零 和 复写零问题

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

IDEA常用AI插件

只推荐免费的 一、对话式AI 1. ChatGPT GPT-4 - Bito AI Code Assistant ChatGPT GPT-4 - Bito AI Code Assistant 插件地址&#xff1a;https://plugins.jetbrains.com/plugin/18289-chatgpt-gpt-4–bito-ai-code-assistant支持自定义prompt支持解释代码支持生成代码注释支持…

【2023最新版】Python全栈知识点总结

python全栈知识点总结 全栈即指的是全栈工程师&#xff0c;指掌握多种技能&#xff0c;并能利用多种技能独立完成产品的人。就是与这项技能有关的都会&#xff0c;都能够独立的完成。 全栈只是个概念&#xff0c;也分很多种类。真正的全栈工程师涵盖了web开发、DBA 、爬虫 、…

Bootstrap的媒体对象组件(图文展示组件),挺有用的一个组件。

Bootstrap的.media类是用于创建媒体对象的&#xff0c;媒体对象通常用于展示图像&#xff08;图片&#xff09;和文本内容的组合&#xff0c;这种布局在展示新闻文章、博客帖子等方面非常常见。.media类使得创建这样的媒体对象非常简单&#xff0c;通常包含一个图像和相关的文本…

操作系统——进程同步

&#xff08;一&#xff09;简答题 1.什么是临界资源&#xff1f;什么是临界区&#xff1f; 答&#xff1a;临界资源是指每次仅允许一个进程访问的资源。属于临界资源有硬件打印机、磁带机等,软件在消息缓冲队列、变量、数组、缓冲区等。 (2)不论是硬件临界资源,还是软件临界资…

CEC2013(MATLAB):狐猴优化算法(Lemurs Optimizer,LO)求解CEC2013(提供MATLAB代码及参考文献)

一、狐猴优化算法 狐猴优化算法&#xff08;Lemurs Optimizer&#xff0c;LO&#xff09;由Ammar Kamal Abasi等人于2022年提出&#xff0c;该算法模拟狐猴的跳跃和跳舞行为&#xff0c;具有结构简单&#xff0c;思路新颖&#xff0c;搜索速度快等优势。狐猴优化算法&#xff…

Java身份证OCR识别 - 阿里云API【识别准确率超过99%】

1. 阿里云API市场 https://market.aliyun.com/products/57124001/cmapi00063618.html?spm5176.28261954.J_7341193060.41.60e52f3drduOTh&scm20140722.S_market%40%40API%E5%B8%82%E5%9C%BA%40%40cmapi00063618._.ID_market%40%40API%E5%B8%82%E5%9C%BA%40%40cmapi0006361…

AI向量数据库

矢量数据库是一种将数据存储为高维向量的数据库&#xff0c;高维向量是特征或属性的数学表示。 每个向量都有一定数量的维度&#xff0c;范围从几十到几千不等&#xff0c;具体取决于数据的复杂性和粒度。 矢量数据库&#xff08;Vector Database&#xff09;和矢量开发库&…

《视觉 SLAM 十四讲》V2 第 10 讲 后端优化2 简化BA 【位姿图】

文章目录 第10讲 后端210.1 滑动窗口滤波 和 优化10.1.2 滑动窗口法 10.2 位姿图10.3 实践&#xff1a; 位姿图优化本讲 CMakeLists.txt 10.3.1 g2o 原生位姿图 【Code】10.3.2 李代数上的位姿优化 【Code】 习题10题1 【没推完】 LaTex 第10讲 后端2 滑动窗口优化 位姿图优化…

【iOS】使用单例封装通过AFNetworking实现的网络请求

这里写目录标题 前言单例封装网络请求1. 首先创建一个继承于NSObject的单例类&#xff0c;笔者这里以Manager对单例类进行命名&#xff0c;然后声明并实现单例类的初始化方法2.实现完单例的创建方法后我们即可通过AFNetworking中的GET方法进行网络请求3.在Controller文件中创建…

音频录制和处理软件 Audio Hijack mac中文版说明

Audio Hijack mac是一款功能强大的音频录制和处理软件&#xff0c;它可以帮助用户从各种来源捕获和处理音频。 首先&#xff0c;Audio Hijack具有灵活的音频捕获功能。它支持从多个来源录制音频&#xff0c;包括麦克风、应用程序、网络流媒体、硬件设备等等。你可以选择捕获整个…

2022最新版-李宏毅机器学习深度学习课程-P26 Recurrent Neural Network

RNN 应用场景&#xff1a;填满信息 把每个单词表示成一个向量的方法&#xff1a;独热向量 还有其他方法&#xff0c;比如&#xff1a;Word hashing 单词哈希 输入&#xff1a;单词输出&#xff1a;该单词属于哪一类的概率分布 由于输入是文字序列&#xff0c;这就产生了一个问…

云表|低代码开发崛起:重新定义企业级应用开发

低代码开发这个概念在近年来越来越受到人们的关注&#xff0c;市场对于低代码的需求也日益增长。据Gartner预测&#xff0c;到2025年&#xff0c;75&#xff05;的大型企业将使用至少四种低代码/无代码开发工具&#xff0c;用于IT应用开发和公民开发计划。 那么&#xff0c;为什…

新的 Work Node 如何加入 K8s 集群 - Kubeadm ?

Author&#xff1a;rab 1、新的 work node 节点安装 kubelet、kubeadm 添加 k8s 镜像源 cat <<EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgch…