前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >爬取京东手机信息

爬取京东手机信息

作者头像
Remember_Ray
发布2020-09-15 10:08:54
1.2K0
发布2020-09-15 10:08:54
举报
文章被收录于专栏:Ray学习笔记Ray学习笔记

爬虫案例

学习了HttpClient和Jsoup,就掌握了如何抓取数据和如何解析数据,接下来,我们做一个小练习,把京东的手机数据抓取下来。

主要目的是HttpClient和Jsoup的学习。

需求分析

首先访问京东,搜索手机,分析页面,我们抓取以下商品数据:

商品图片、价格、标题、商品详情页

SPU和SKU

除了以上四个属性以外,我们发现上图中的苹果手机有四种产品,我们应该每一种都要抓取。那么这里就必须要了解spu和sku的概念

SPU = Standard Product Unit (标准产品单位)

SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

例如上图中的苹果手机就是SPU,包括红色、深灰色、金色、银色

SKU=stock keeping unit(库存量单位)

SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。

例如上图中的苹果手机有几个款式,红色苹果手机,就是一个sku

数据库表分析

根据需求分析,我们创建的表如下

代码语言:javascript
复制
CREATE TABLE `jd_item` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `spu` bigint(15) DEFAULT NULL COMMENT '商品集合id',
  `sku` bigint(15) DEFAULT NULL COMMENT '商品最小品类单元id',
  `title` varchar(100) DEFAULT NULL COMMENT '商品标题',
  `price` bigint(10) DEFAULT NULL COMMENT '商品价格',
  `pic` varchar(200) DEFAULT NULL COMMENT '商品图片',
  `url` varchar(200) DEFAULT NULL COMMENT '商品详情地址',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `sku` (`sku`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='京东商品表';

项目示例

使用Spring Boot + Spring Data JPA和定时任务进行开发

添加依赖

代码语言:javascript
复制
<?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.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ray</groupId>
    <artifactId>crawler-jd</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>crawler-jd</name>
    <description>京东爬虫示例</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--SpringMVC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringData Jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--MySQL连接包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- HttpClient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <!--Jsoup-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.3</version>
        </dependency>

        <!--工具包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

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

</project>

配置文件

代码语言:javascript
复制
#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ray0804?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true
# 每次启动清空表,可不用
spring.jpa.hibernate.ddl-auto=create

代码实现

代码语言:javascript
复制
@Entity
@Table(name = "jd_item")
public class Item {

    //主键
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    //标准产品单位(商品集合)
    private Long spu;
    //库存量单位(最小品类单元)
    private Long sku;
    //商品标题
    private String title;
    //商品价格
    private Double price;
    //商品图片
    private String pic;
    //商品详情地址
    private String url;
    //创建时间
    private Date created;
    //更新时间
    private Date updated;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getSpu() {
        return spu;
    }

    public void setSpu(Long spu) {
        this.spu = spu;
    }

    public Long getSku() {
        return sku;
    }

    public void setSku(Long sku) {
        this.sku = sku;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getPic() {
        return pic;
    }

    public void setPic(String pic) {
        this.pic = pic;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }
}
代码语言:javascript
复制
public interface ItemDao extends JpaRepository<Item, Long> {
}
代码语言:javascript
复制
public interface ItemService {

    //根据条件查询数据
    List<Item> findAll(Item item);

    //保存数据
    void save(Item item);
}
代码语言:javascript
复制
@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private ItemDao itemDao;

    @Override
    public List<Item> findAll(Item item) {
        Example example = Example.of(item);
        List list = this.itemDao.findAll(example);
        return list;
    }

    @Override
    @Transactional
    public void save(Item item) {
        this.itemDao.save(item);
    }
}
封装HttpClient

我们需要经常使用HttpClient,所以需要进行封装,方便使用

代码语言:javascript
复制
@Component
public class HttpUtils {

    // 响应成功
    private final int SUCCESS_CODE = 200;

    private final PoolingHttpClientConnectionManager manager;

    public HttpUtils() {
        this.manager = new PoolingHttpClientConnectionManager();
        // 设置最大连接数
        this.manager.setMaxTotal(200);
        // 设置每个主机的并发数
        this.manager.setDefaultMaxPerRoute(20);
    }

    /**
     * @Description: 根据请求地址 url 下载页面数据
     * @Author: Ray
     * @Date: 2020/8/4 0004 16:05
     **/
    public String getHtml(String url) {
        // 获取 HttpClient 对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(manager).build();

        // 声明 httpGet 请求对象
        HttpGet httpGet = new HttpGet(url);
        // 设置请求参数 RequestConfig
        httpGet.setConfig(this.getConfig());
        // 设置一下头信息:模拟环境
        httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0");

        // 在外面声明,方便后面的关闭操作
        CloseableHttpResponse response = null;

        try {
            // 使用HttpClient发起请求,返回response
            response = httpClient.execute(httpGet);
            // 解析response返回数据
            if (response.getStatusLine().getStatusCode() == SUCCESS_CODE) {
                String html = "";
                // 如果response。getEntity获取的结果是空,在执行EntityUtils.toString会报错
                // 需要对Entity进行非空的判断
                if (response.getEntity() != null) {
                    html = EntityUtils.toString(response.getEntity(), "utf8");
                }
                return html;
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                // 不能关闭,现在使用的是连接管理器
                // httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * @Description: 下载图片
     * @Author: Ray
     * @Date: 2020/8/4 0004 16:07
     **/
    public String getImage(String url) {
        // 获取 HttpClient 对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(manager).build();
        // 声明httpGet请求对象
        HttpGet httpGet = new HttpGet(url);
        // 设置请求参数RequestConfig
        httpGet.setConfig(this.getConfig());

        CloseableHttpResponse response = null;

        try {
            // 使用HttpClient发起请求,返回response
            response = httpClient.execute(httpGet);
            // 解析response下载图片
            if (response.getStatusLine().getStatusCode() == SUCCESS_CODE) {
                if (response.getEntity() != null) {
                    // 获取文件后缀
                    String extName = url.substring(url.lastIndexOf("."));
                    // 使用uuid生成图片名
                    String imageName = UUID.randomUUID().toString() + extName;
                    // 指定保存的目录,不存在会创建
                    String dir = "D:/images/";
                    File dirPath = new File(dir);
                    if (!dirPath.exists()) {
                        dirPath.mkdirs();
                    }
                    // 声明输出的文件
                    File file = new File(dir + imageName);
                    OutputStream outputStream = new FileOutputStream(file);
                    // 使用响应体输出文件,writeTo 写入到哪里
                    response.getEntity().writeTo(outputStream);
                    // 返回生成的图片名
                    return imageName;
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    // 关闭连接
                    response.close();
                }
                // 不能关闭,现在使用的是连接管理器
                // httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * @Description: 获取请求参数对象
     * @Author: Ray
     * @Date: 2020/8/4 0004 16:13
     **/
    private RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom()
                // 设置创建连接的超时时间
                .setConnectTimeout(1000)
                // 设置获取连接的超时时间
                .setConnectionRequestTimeout(500)
                // 设置数据传输的超时时间
                .setSocketTimeout(10000)
                .build();
        return config;
    }
}
实现数据抓取

使用定时任务,可以定时抓取最新的数据

代码语言:javascript
复制
@Component
public class ItemTask {

    @Autowired
    private HttpUtils httpUtils;
    @Autowired
    private ItemService itemService;

    public static final ObjectMapper MAPPER = new ObjectMapper();

    //设置定时任务执行完成后,再间隔100秒执行一次
    @Scheduled(fixedDelay = 1000 * 100)
    public void process() throws Exception {
        // https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&page=1&s=1&click=0
        String url = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&click=0&page=";

        // 遍历执行,获取所有数据
        for (int i = 1; i <= 10; i++) {
            //发起请求进行访问,获取页面数据,先访问第一页
            String newUrl = "";
            if (i > 1) {
                int page = i+2;
                newUrl = url + page + "&s=" + ((50 * (i-1)) + 1);
            } else {
                newUrl = url + i + "&s=" + i;
            }
            System.out.println(newUrl);
            String html = this.httpUtils.getHtml(newUrl);

            //解析页面数据,保存数据到数据库中
            this.parseHtml(html);
        }
        System.out.println("执行完毕");
    }

    //解析页面,并把数据保存到数据库中
    private void parseHtml(String html) throws Exception {
        //System.out.println(html);

        //使用jsoup解析页面
        Document document = Jsoup.parse(html);

        //获取商品数据 - 直接子元素
        Elements spus = document.select("div#J_goodsList > ul > li");

        //遍历商品spu数据
        for (Element spuEle : spus) {
            //获取商品spu
            // 2020年8月7日14:52:38 发现 spu 为空的情况,加了个判断
            String spu = spuEle.attr("data-spu");
            long spuId = 0L;
            if (StringUtils.isNotBlank(spu)) {
                spuId = Long.parseLong(spu);
            }

            //获取商品sku数据
            Elements skus = spuEle.select("li.ps-item img");
            // 获取商品sku
            for (Element skuEle : skus) {
                Long skuId = Long.parseLong(skuEle.attr("data-sku"));

                //判断商品是否被抓取过,可以根据sku判断
                Item param = new Item();
                param.setSku(skuId);
                List<Item> list = this.itemService.findAll(param);
                //判断是否查询到结果
                if (!list.isEmpty()) {
                    //如果有结果,表示商品已下载,进行下一次遍历
                    continue;
                }

                //保存商品数据,声明商品对象
                Item item = new Item();
                //商品spu
                item.setSpu(spuId);
                //商品sku
                item.setSku(skuId);
                //商品url地址
                item.setUrl("https://item.jd.com/" + skuId +".html");
                //创建时间
                item.setCreated(new Date());
                //修改时间
                item.setUpdated(item.getCreated());
                //获取商品标题
                String itemHtml = this.httpUtils.getHtml(item.getUrl());
                String title = Jsoup.parse(itemHtml).select("div.sku-name").text();
                item.setTitle(title);
                //获取商品价格
                String priceUrl = "https://p.3.cn/prices/mgets?skuIds=J_" + skuId;
                String priceJson = this.httpUtils.getHtml(priceUrl);
                // 解析 json 数据获取商品价格
                double price = MAPPER.readTree(priceJson).get(0).get("p").asDouble();
                item.setPrice(price);
                //获取图片地址
                if (StringUtils.isNoneBlank(skuEle.attr("data-lazy-img"))) {
                    // n9 换成 n1 使图片变大
                    String picUrl = "https:" + skuEle.attr("data-lazy-img").replace("/n9/", "/n1/");
                    System.out.println(picUrl);
                    // 下载图片
                    String picName = this.httpUtils.getImage(picUrl);
                    item.setPic(picName);
                }

                // 保存商品数据
                this.itemService.save(item);
            }
        }
    }
}
引导类

开启定时任务

代码语言:javascript
复制
@SpringBootApplication
// 开启定时任务
@EnableScheduling
public class CrawlerJdApplication {
    public static void main(String[] args) {
        SpringApplication.run(CrawlerJdApplication.class, args);
    }
}

输出结果

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-07|,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 爬虫案例
    • 需求分析
      • SPU和SKU
    • 数据库表分析
      • 项目示例
        • 添加依赖
        • 配置文件
        • 代码实现
      • 输出结果
      相关产品与服务
      云数据库 MySQL
      腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档