Jsoup이란?
자바로 만들어진 HTML 파서로 URL, 파일, 문자열을 소스로 하여 HTML을 파싱할 수 있는 자바 라이브러리
Jsoup, Selenium 차이
Jsoup은 백그라운드에서 HTTP Request/Response가 이루어지며, Request를 던졌을 때 웹 서버에서 응답한 결과를 받아온다. 따라서, 서버 사이드 랜더링(SSR, Server-Side Rendering)을 사용하는 웹 사이트는 서버에서 랜더링을 한 후 화면을 그리기 때문에 크롤링이 가능하지만, 클라이언트 사이드 랜더링(CSR, Client-Side Redering)을 사용하는 웹 사이트는 최소한의 페이지만 서버에서 랜더링하고 클라이언트(브라우저)에서 나머지 화면을 랜더링하기 때문에
HTTP Request로는 실제 브라우저에서 보여지는 화면을 스크랩 할 수 없다.
하지만, Selenium은 현재 브라우저에 출력된 페이지의 소스를 파싱 할 수 있다는 특성을 이용하여 CSR을 사용하는 웹 사이트도 크롤링 할 수 있다. 또한, XPath를 지원하고 Javascript 명령어를 실행할 수 있기 때문에 Jsoup보다 많은 데이터를 크롤링 할 수 있다.
Selenium을 이용한 크롤러가 Jsoup보다 마냥 좋은 것은 아니다. Jsoup은 HTTP Request를 통해 웹서버에 직접 요청하기 때문에 빠른 응답을 받을 수 있다. 하지만, Selenium은 브라우저가 랜더링 된 후 페이지를 파싱하기 때문에 수집 속도가 느리다. 따라서, 동적 웹페이지가 아니라면 Jsoup을 이용하는 것이 좋다.
출처: https://heodolf.tistory.com/103
[크롤링] Selenium을 이용한 JAVA 크롤러 (1) - HTML 파싱
2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (1) - HTML 파싱 2020/02/25 - [Back-end/JAVA] - [크롤링] Jsoup을 이용한 JAVA 크롤러 (2) - 파일 다운로드 0. 서론 이전 포스트에서 Js..
heodolf.tistory.com
동적 웹페이지가 아니기 때문에 selenium 대신 전에 사용했던 Jsoup을 다시 사용해봤다
웹 페이지에서 정보를 가져와서 리사이클러뷰에 출력하는 실습을 해봤다
사용 예제
파싱한 페이지: https://ba-on.com/product/list.html?cate_no=34
모듈 설치
// build.gradle
// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
// Jsoup
implementation 'org.jsoup:jsoup:1.13.1'
인터넷 설정
// AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
크롤링 구현 함수
fun crawling(){
Thread(Runnable{
val url = "https://ba-on.com/product/list.html?cate_no=34"
val doc = Jsoup.connect(url).get()
val imgList = doc.select("div.thumbnail")
val titleList = doc.select("div.description")
for(i in imgList.indices){
val item_img = "https:"+imgList[i].select("a img").attr("src")
val item_title = titleList[i].select("div.description").select("a ul li span").text()
//Log.d("MyActivity",item_img.toString())
//Log.d("MyActivity",item_title)
productList.add(Product(item_img,item_title))
}
runOnUiThread{
val productRvAdapter=ProductRVAdapter(productList,this)
binding.productListRv.adapter=productRvAdapter
binding.productListRv.layoutManager=LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
}).start()
}
1. http 통신을 하기 때문에 메인 스레드와 비동기적으로 작동해야해서 thread를 만들어서 사용
2. 파싱하고자 하는 웹 페이지 연결
val url = "https://ba-on.com/product/list.html?cate_no=34"
val doc = Jsoup.connect(url).get()
3. 필요한 정보를 추출하기 위해 필요한 부분의 태그를 리스트로 가져옴
val imgList = doc.select("div.thumbnail")
val titleList = doc.select("div.description")
4. 실제 이미지와 텍스트 가져오기
상대주소로 되어있기 때문에 "https:" 추가해줌
val item_img = "https:"+imgList[i].select("a img").attr("src")
val item_title = titleList[i].select("a ul li span").text()
5. recyclerView 어댑터 설정
runOnUiThread{
val productRvAdapter=ProductRVAdapter(productList,this)
binding.productListRv.adapter=productRvAdapter
binding.productListRv.layoutManager=LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
recyclerView 어댑터 설정은 크롤링 후에 실행되어야 하기 때문에 스레드 내에 runOnUiThread 사용해서 작성
6. 이미지는 glide 사용해서 출력
// recyclerView Adapter
inner class ViewHolder(val binding: ShoppingProductBinding,val context:Context): RecyclerView.ViewHolder(binding.root){
fun bind(product:Product){
Glide.with(context)
.load(product.productSrc)
.into(binding.itemIv)
binding.itemTv.text=product.productName
}
}
Glide에서 context가 필요해서 ViewHolder의 매개변수로 context를 받도록 했음
실습 코드
data class
package com.example.jsouptest
data class Product(
val productSrc: String,
val productName: String
)
adapter 전체 코드
package com.example.jsouptest
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.jsouptest.databinding.ShoppingProductBinding
class ProductRVAdapter(private var productList:ArrayList<Product>,val context:Context):RecyclerView.Adapter<ProductRVAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductRVAdapter.ViewHolder {
val binding: ShoppingProductBinding = ShoppingProductBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return ViewHolder(binding,context)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(productList[position])
}
override fun getItemCount(): Int =productList.size
inner class ViewHolder(val binding: ShoppingProductBinding,val context:Context): RecyclerView.ViewHolder(binding.root){
fun bind(product:Product){
Glide.with(context)
.load(product.productSrc)
.into(binding.itemIv)
binding.itemTv.text=product.productName
}
}
}
전체 코드
package com.example.jsouptest
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.jsouptest.databinding.ActivityShopBinding
import org.jsoup.Jsoup
class ShopActivity:AppCompatActivity() {
lateinit var binding: ActivityShopBinding
var productList = ArrayList<Product>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShopBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
onClickEvent()
crawling()
}
fun onClickEvent(){
binding.gotoMainActivityBtn.setOnClickListener {
finish()
}
}
fun crawling(){
Thread(Runnable{
val url = "https://ba-on.com/product/list.html?cate_no=34"
val doc = Jsoup.connect(url).get()
val imgList = doc.select("div.thumbnail")
val titleList = doc.select("div.description")
for(i in imgList.indices){
val item_img = "https:"+imgList[i].select("a img").attr("src")
val item_title = titleList[i].select("a ul li span").text()
//Log.d("MyActivity",item_img.toString())
//Log.d("MyActivity",item_title)
productList.add(Product(item_img,item_title))
}
runOnUiThread{
val productRvAdapter=ProductRVAdapter(productList,this)
binding.productListRv.adapter=productRvAdapter
binding.productListRv.layoutManager=LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
}
}).start()
}
}
실행 화면
근데 html 태그가 복잡해질수록 크롤링이 잘 안 돼서 다음엔 셀레니움을 써서 해볼 생각이다
'Native App > 👽Android' 카테고리의 다른 글
adb 명령어를 사용해서 안드로이드 기기 무선 페어링 (0) | 2024.07.06 |
---|---|
firebase 연동 정리 (0) | 2023.01.14 |
kakao login api 사용 - 2 (0) | 2022.06.19 |
kakao login api 사용 - 1 (0) | 2022.06.16 |
#02 SharedPreferences (0) | 2022.06.16 |
댓글