草庐IT

Java如何解析html里面的内容并存到数据库

吳名氏 2023-08-10 原文

一、前言

        最近接到一个任务,需要爬取五级行政区划的所有数据(大概71万条数据在),需要爬取的网站:行政区划 - 行政区划代码查询 发现这个网站不是用接口请求的,而且直接返回html代码,所以,去看了一下Java是如何解析html里面的内容

二、准备工作

        我选用的是使用jsoup进行html的读取和解析,需要加入如下依赖:

        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.8.3</version>
        </dependency>

        jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jquery的操作方法来取出和操作数据。它是基于MIT协议发布的。

        jsoup的主要功能如下:

        1、从一个URL,文件或字符串中解析HTML;

        2、使用DOM或CSS选择器来查找、取出数据;

        3、可操作HTML元素、属性、文本; 

        示例代码:

//获取html的文档对象
Document doc = Jsoup.parse("http://www.dangdang.com");
//获取页面下id="content"的标签
Element content = doc.getElementById("content");
//获取页面下的a标签
Elements links = content.getElementsByTag("a");
for (Element link : links) {
    //获取a标签下的href的属性值
    String linkHref = link.attr("href");
    //获取a标签下的文本内容
    String linkText = link.text();
}

        Elements这个对象提供了一系列类似于DOM的方法来查找元素,抽取并处理其中的数据。具体如下:

        1、查找元素

        getElementById(String id)

        getElementsByTag(String tag)

        getElementsByClass(String className)

        getElementsByAttribute(String key) (and related methods)

        Element siblings: siblingElements(), firstElementSibling(), lastElementSibling();nextElementSibling(), previousElementSibling()

        Graph: parent(), children(), child(int index)

        2、元素数据

        attr(String key)获取属性

        attr(String key, String value)设置属性

        attributes()获取所有属性

        id(), className() and classNames()

        text()获取文本内容

        text(String value) 设置文本内容

        html()获取元素内

        HTMLhtml(String value)设置元素内的HTML内容

        outerHtml()获取元素外HTML内容

        data()获取数据内容(例如:script和style标签)

        tag() and tagName()

        3、操作HTML和文本

        append(String html), prepend(String html)

        appendText(String text), prependText(String text)

        appendElement(String tagName), prependElement(String tagName) html(String value)

三、开始爬取网站数据

        直接上代码:

        Test.java:

@Slf4j
@SpringBootTest
class Test {

    @Resource
    private PositionService positionService;

    /**
     * 爬取省市区网站
     */
    @Test
    public void test2() throws InterruptedException {
        //一共五级
        for (int i = 0 ; i < 5 ; i++) {
            if (i == 0) {
                List<PositionEntity> positionEntities = PositionUtils.reqPosition(PositionUtils.URL_HEAD);
                savePosition(positionEntities, null, i);
                continue;
            }
            List<Position> positions = positionService.findListByLevel(i);
            for (Position parentPosition : positions) {
                List<PositionEntity> positionEntities = PositionUtils.reqPosition(String.format("%s%s%s", PositionUtils.URL_HEAD, parentPosition.getSn(), PositionUtils.URL_TAIL));
                savePosition(positionEntities, parentPosition, i);
            }
        }
    }

    /**
     * 报错地址信息
     */
    private void savePosition(List<PositionEntity> positionEntities, Position parentPosition, int i){
        for (PositionEntity entity : positionEntities) {
            Position position = new Position();
            position.setSn(entity.getCode());
            position.setFullInitials(PinyinUtils.strFirst2Pinyin((parentPosition != null ? parentPosition.getFullName() : "")+entity.getName()));
            position.setFullName((parentPosition != null ? parentPosition.getFullName() : "")+entity.getName());
            position.setLevel(i + 1);
            position.setName(entity.getName());
            position.setOrderNumber(0);
            position.setPsn(parentPosition != null ? parentPosition.getSn() : 0L);
            long count = positionService.countBySn(position.getSn());
            if (count == 0) {
                positionService.savePosition(position);
            }
        }
    }

}

         PositionService.java:

public interface PositionService {

    void savePosition(Position position);

    long countBySn(Long sn);

    List<Position> findListByLevel(Integer level);
}

         PositionServiceImpl.java:

@Service
public class PositionServiceImpl extends ServiceImpl<PositionMapper, Position> implements PositionService {

    @Override
    public void savePosition(Position position) {
        baseMapper.insert(position);
    }

    @Override
    public long countBySn(Long sn) {
        return baseMapper.selectCount(new QueryWrapper<Position>().lambda().eq(Position::getSn, sn));
    }

    @Override
    public List<Position> findListByLevel(Integer level) {
        return baseMapper.selectList(new QueryWrapper<Position>().lambda().eq(Position::getLevel, level));
    }
}

        PositionMapper.java:

@Repository
public interface PositionMapper extends BaseMapper<Position> {

}

        Position.java:

@Data
@TableName("position")
@EqualsAndHashCode()
public class Position implements Serializable {

    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 编码
     */
    private Long sn;

    /**
     * 上级地址编码
     */
    private Long psn;

    /**
     * 名称
     */
    private String name;

    /**
     * 简称
     */
    private String shortName;

    /**
     * 层级
     */
    private Integer level;

    /**
     * 区号
     */
    private String code;

    /**
     * 邮政编码
     */
    private String zip;

    /**
     * 拼音
     */
    private String spell;

    /**
     * 拼音首字母
     */
    private String spellFirst;

    /**
     * 地址全名
     */
    private String fullName;

    /**
     * 地址全名拼音首字母
     */
    private String fullInitials;

    /**
     * 排序
     */
    private Integer orderNumber;

}

        PositionMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wkf.workrecord.dao.PositionMapper">

</mapper>

        PositionUtils.java:

public class PositionUtils {

    public final static String URL_HEAD = "https://xingzhengquhua.bmcx.com/";

    public final static String URL_TAIL = "__xingzhengquhua/";

    public static List<PositionEntity> reqPosition(String url) throws InterruptedException {
        String htmlStr = HttpUtils.getRequest(url);
        //解析字符串为Document对象
        Document doc = Jsoup.parse(htmlStr);
        //获取body元素,获取class="fc"的table元素
        Elements table = doc.body().getElementsByAttributeValue("bgcolor", "#C5D5C5");
        //获取tbody元素
        Elements children;
        children = table.first().children();
        //获取tr元素集合
        Elements tr = children.get(0).getElementsByTag("tr");
        List<PositionEntity> result = new ArrayList<>();
        //遍历tr元素,获取td元素,并打印
        for (int i = 3; i < tr.size(); i++) {
            Element e1 = tr.get(i);
            Elements td = e1.getElementsByTag("td");
            if (td.size() < 2) {
                break;
            }
            String name = td.get(0).getElementsByTag("td").first().getElementsByTag("a").text();
            String code = td.get(1).getElementsByTag("td").first().getElementsByTag("a").text();
            if (CheckUtils.isEmpty(name) || CheckUtils.isEmpty(code)) {
                continue;
            }
            result.add(new PositionEntity(name, Long.parseLong(code)));
        }
        //防止ip被封
        Thread.sleep(10000);
        return result;
    }

}

        PinyinUtils.java:

public class PinyinUtils {
	private final static HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
	static {
		format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
		format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
		format.setVCharType(HanyuPinyinVCharType.WITH_V);
	}

	/**
	 * 字符串转拼音
	 * 
	 * @param str
	 *            中文字符串
	 * @param fill
	 *            分隔符
	 * @return 返回中文的拼音串
	 */
	public static String str2Pinyin(String str, String fill) {
		if (null == str) {
			return null;
		}
		try {
			StringBuilder sb = new StringBuilder();
			if (fill == null)
				fill = "";
			boolean isCn = true;
			for (int i = 0; i < str.length(); i++) {
				char c = str.charAt(i);
				if (i > 0 && isCn) {
					sb.append(fill);
				}
				if (c == ' ') {
					sb.append(fill);
				}
				// 1、判断c是不是中文
				if (c >= '\u4e00' && c <= '\u9fa5') {
					isCn = true;
					String[] piyins = PinyinHelper.toHanyuPinyinStringArray(c, format);
					if (null == piyins || 0 >= piyins.length) {
						continue;
					}
					sb.append(piyins[0]);
				} else {
					// 不是中文
					if (c >= 'A' && c <= 'Z') {
						sb.append((char)(c + 32));
					} else {
						sb.append(c);
					}
					isCn = false;
				}
			}
			return sb.toString();
		} catch (BadHanyuPinyinOutputFormatCombination e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 拼音首字母
	 * 
	 * @param str
	 *            中文字符串
	 * @return 中文字符串的拼音首字母
	 */
	public static String strFirst2Pinyin(String str) {
		if (null == str) {
			return null;
		}
		try {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < str.length(); i++) {
				char c = str.charAt(i);
				// 1、判断c是不是中文
				if (c >= '\u4e00' && c <= '\u9fa5') {
					String[] piyins = PinyinHelper.toHanyuPinyinStringArray(c, format);
					if (null == piyins || 0 >= piyins.length) {
						continue;
					}
					sb.append(piyins[0].charAt(0));
				} else {
					// 不是中文
					if (c >= 'A' && c <= 'Z') {
						sb.append((char)(c + 32));
					} else {
						sb.append(c);
					}
				}
			}
			return sb.toString();
		} catch (BadHanyuPinyinOutputFormatCombination e) {
			e.printStackTrace();
		}
		return null;
	}
}

有关Java如何解析html里面的内容并存到数据库的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  10. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

随机推荐