Ver3.0 ๐ท๏ธ ์๋ฐ ์น ํฌ๋กค๋ง๊ณผ Jsoup ์์ ์ ๋ณต

๐ท๏ธ ์๋ฐ ์น ํฌ๋กค๋ง๊ณผ Jsoup ์์ ์ ๋ณต
์๋ , ์น๊ตฌ! ๐ ์ค๋์ ์๋ฐ ๊ฐ๋ฐ์๋ผ๋ฉด ๊ผญ ์์์ผ ํ ์น ํฌ๋กค๋ง์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊น ํด. ์ธํฐ๋ท์๋ ์์ฒญ๋ ์์ ๋ฐ์ดํฐ๊ฐ ์์์? ๋ด์ค ๊ธฐ์ฌ, ์ํ ์ ๋ณด, ์ฃผ์ ์์ธ, ๋ ์จ ์ ๋ณด ๋ฑ๋ฑ... ์ด๋ฐ ๋ฐ์ดํฐ๋ค์ ์ผ์ผ์ด ์์ผ๋ก ๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ ํ๋ฉด ์ ๋ง ๋์ฐํ์ง ์๊ฒ ์ด? ๐คฏ
๋ฐ๋ก ์ด๋ด ๋ ํ์ํ ๊ฒ ์น ํฌ๋กค๋ง์ด์ผ. ๊ทธ๋ฆฌ๊ณ ์๋ฐ์์ ์น ํฌ๋กค๋ง์ ํ ๋ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ฐ๋ก **Jsoup**์ด์ง. ๋ง์น ์นํ์ด์ง๋ฅผ ์๋ฆฌํ๋ ์๋ฆฌ์ฌ์ฒ๋ผ, Jsoup์ HTML ๋ฌธ์๋ฅผ ์์ฃผ ์ฝ๊ฒ ํ์ฑํ๊ณ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ์์ ๋ฝ์๋ผ ์ ์๊ฒ ํด์ค. ๐ณ
๐ฏ ์น ํฌ๋กค๋ง์ด ๋ญ๊ธธ๋?
์น ํฌ๋กค๋ง(Web Crawling)์ ์ฝ๊ฒ ๋งํด์ ์น์ฌ์ดํธ๋ฅผ ์๋์ผ๋ก ๋์๋ค๋๋ฉฐ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ ๊ธฐ์ ์ด์ผ. ๋ง์น ๊ฑฐ๋ฏธ(Spider)๊ฐ ๊ฑฐ๋ฏธ์ค์ ํ๊ณ ์ด๋ํ๋ฏ์ด, ํ๋ก๊ทธ๋จ์ด ์นํ์ด์ง๋ฅผ ๋ฐ๋ผ๋ค๋๋ฉฐ ์ ๋ณด๋ฅผ ๊ธ์ด๋ชจ์ผ๋ ๊ฑฐ์ง. ๊ทธ๋์ ์น ํฌ๋กค๋ฌ๋ฅผ '์คํ์ด๋'๋ผ๊ณ ๋ถ๋ฅด๊ธฐ๋ ํด. ๐ท๏ธ
โข ์น ํฌ๋กค๋ง: ์น์ฌ์ดํธ๋ฅผ ๊ด๋ฒ์ํ๊ฒ ํ์ํ๋ฉฐ ๋ฐ์ดํฐ๋ฅผ ์์ง (๊ฒ์์์ง์ด ํ๋ ์ผ)
โข ์น ์คํฌ๋ํ: ํน์ ์นํ์ด์ง์์ ์ํ๋ ๋ฐ์ดํฐ๋ง ์ถ์ถ (์ฐ๋ฆฌ๊ฐ ์ฃผ๋ก ํ๋ ์ผ)
ํ์ง๋ง ์ค๋ฌด์์๋ ๋ ์ฉ์ด๋ฅผ ๊ฑฐ์ ๊ฐ์ ์๋ฏธ๋ก ์ฌ์ฉํ๋๊น ๋๋ฌด ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ผ! ๐
๐ก ์ ์น ํฌ๋กค๋ง์ ๋ฐฐ์์ผ ํ ๊น?
์, ์ด์ ์ค์ง์ ์ธ ์ด์ผ๊ธฐ๋ฅผ ํด๋ณด์. ์น ํฌ๋กค๋ง์ ๋ฐฐ์ฐ๋ฉด ๋ญ๊ฐ ์ข์๊น? ๐ค
1. ์์ฅ ์กฐ์ฌ ์๋ํ
๊ฒฝ์์ฌ ์ ํ ๊ฐ๊ฒฉ์ ๋งค์ผ ์ฒดํฌํด์ผ ํ๋ค๋ฉด? ์๋ฐฑ ๊ฐ์ ์ฌ์ดํธ๋ฅผ ์ผ์ผ์ด ๋ฐฉ๋ฌธํ ํ์ ์์ด ํฌ๋กค๋ฌ๊ฐ ์๋์ผ๋ก ์์งํด์ค. ์ด์ปค๋จธ์ค ์
๊ณ์์๋ ํ์ ๊ธฐ์ ์ด์ง! ๐ฐ
2. ๋ฐ์ดํฐ ๋ถ์ ํ๋ก์ ํธ
๋น
๋ฐ์ดํฐ ๋ถ์์ ํ๋ ค๋ฉด ๋จผ์ ๋ฐ์ดํฐ๊ฐ ์์ด์ผ ํ์์? ์น ํฌ๋กค๋ง์ผ๋ก ๋ด์ค ๊ธฐ์ฌ, SNS ๊ฒ์๊ธ, ๋ฆฌ๋ทฐ ๋ฑ์ ์์งํด์ ํธ๋ ๋๋ฅผ ๋ถ์ํ ์ ์์ด. ๐
3. ์๋ํ ๋ด ๊ฐ๋ฐ
๋ถ๋์ฐ ๋งค๋ฌผ ์๋ฆผ, ํ์ ํ ์ ํ ์ฌ์
๊ณ ์๋ฆผ, ์ฃผ์ ๋ด์ค ๋ชจ๋ํฐ๋ง ๋ฑ ๋ค์ํ ์๋ํ ์๋น์ค๋ฅผ ๋ง๋ค ์ ์์ด. ์ค์ ๋ก **์ฌ๋ฅ๋ท** ๊ฐ์ ํ๋ซํผ์์๋ ์ด๋ฐ ์๋ํ ์๋น์ค ๊ฐ๋ฐ ์ฌ๋ฅ์ด ์ธ๊ธฐ๊ฐ ๋ง๋๋ผ๊ณ ! ๐ค
4. ํฌํธํด๋ฆฌ์ค ๊ฐํ
์ทจ์
์ค๋น์์ด๋ผ๋ฉด? ์น ํฌ๋กค๋ง ํ๋ก์ ํธ๋ ์ ๋ง ์ข์ ํฌํธํด๋ฆฌ์ค๊ฐ ๋ผ. "์ ๋ ์ด๋ฐ ๋ฐ์ดํฐ๋ฅผ ์์งํด์ ์ด๋ฐ ์ธ์ฌ์ดํธ๋ฅผ ๋์ถํ์ต๋๋ค"๋ผ๊ณ ๋งํ ์ ์์ผ๋๊น. โจ
โ Jsoup, ๋๋ ๋๊ตฌ๋?
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก Jsoup์ ๋ํด ์์๋ณด์. Jsoup์ ์๋ฐ๋ก ๋ง๋ค์ด์ง HTML ํ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ผ. 2009๋ ์ ์ฒ์ ๋ฑ์ฅํ์ผ๋๊น ๋ฒ์จ 15๋ ์ด ๋์ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ง. ๐
๐ Jsoup์ ์ฅ์
1. ์์ฒญ๋๊ฒ ์ฌ์ด ์ฌ์ฉ๋ฒ
jQuery ์คํ์ผ์ ์
๋ ํฐ๋ฅผ ์ฌ์ฉํ ์ ์์ด์ ์น ๊ฐ๋ฐ ๊ฒฝํ์ด ์๋ค๋ฉด ๋ฐ๋ก ์ต์ํ๊ฒ ๋๊ปด์ง ๊ฑฐ์ผ. CSS ์
๋ ํฐ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ๋ผ!
2. ๊ฐ๋ ฅํ HTML ํ์ฑ
๊นจ์ง HTML๋ ์ ์ฒ๋ฆฌํด์ค. ์ค์ ์น์ฌ์ดํธ๋ค์ ์๋ฒฝํ HTML์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ, Jsoup์ ์ด๋ฐ ๊ฒ๋ค๋ ์ ํ์ฑํด. ๐
3. XSS ๊ณต๊ฒฉ ๋ฐฉ์ด
์ฌ์ฉ์ ์
๋ ฅ์ ์์ ํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ๋ ์ ๊ณตํด. ๋ณด์๋ ์ฑ๊ธธ ์ ์๋ค๋ ๊ฑฐ์ง!
4. ๊ฐ๋ณ๊ณ ๋น ๋ฆ
์ธ๋ถ ์์กด์ฑ์ด ๊ฑฐ์ ์์ด์ ํ๋ก์ ํธ์ ๋ถ๋ด ์์ด ์ถ๊ฐํ ์ ์์ด. ์ฉ๋๋ ์๊ณ ์ฑ๋ฅ๋ ์ข์! โก
๐ฆ Jsoup ์ค์นํ๊ธฐ
Jsoup์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ํ๋ก์ ํธ์ ์ถ๊ฐํด์ผ๊ฒ ์ง? Maven์ด๋ Gradle์ ์ฌ์ฉํ๋ค๋ฉด ์ ๋ง ๊ฐ๋จํด.
Maven ์ฌ์ฉ์๋ผ๋ฉด:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
Gradle ์ฌ์ฉ์๋ผ๋ฉด:
implementation 'org.jsoup:jsoup:1.17.2'
๋ฒ์ ์ ๊ณ์ ์ ๋ฐ์ดํธ๋๋๊น Maven Central Repository์์ ์ต์ ๋ฒ์ ์ ํ์ธํ๋ ๊ฒ ์ข์. 2024๋ ๊ธฐ์ค์ผ๋ก๋ 1.17.2๊ฐ ์ต์ ์ด์ผ. ๐
๐ ์ฒซ ๋ฒ์งธ ํฌ๋กค๋ง ์ฝ๋ ์์ฑํ๊ธฐ
์ด๋ก ์ ์ด์ ์ถฉ๋ถํด! ์ค์ ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณด์. ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์์ ๋ถํฐ ์์ํ ๊ฒ. ๐
๐ฌ Hello Jsoup!
๋จผ์ ๊ฐ๋จํ๊ฒ ์นํ์ด์ง๋ฅผ ๊ฐ์ ธ์์ ์ ๋ชฉ์ ์ถ๋ ฅํ๋ ์ฝ๋๋ฅผ ๋ง๋ค์ด๋ณผ๊น?
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class HelloJsoup {
public static void main(String[] args) {
try {
// ์นํ์ด์ง ์ฐ๊ฒฐ ๋ฐ HTML ๊ฐ์ ธ์ค๊ธฐ
String url = "https://example.com";
Document doc = Jsoup.connect(url).get();
// ํ์ด์ง ์ ๋ชฉ ์ถ์ถ
String title = doc.title();
System.out.println("ํ์ด์ง ์ ๋ชฉ: " + title);
} catch (Exception e) {
e.printStackTrace();
}
}
}
์! ์ ๋ง ๊ฐ๋จํ์ง? ๐ ๋จ ๋ช ์ค์ ์ฝ๋๋ก ์นํ์ด์ง๋ฅผ ๊ฐ์ ธ์์ ์ ๋ชฉ์ ์ถ์ถํ์ด. ํ๋์ฉ ๋ฏ์ด๋ณด์.
1. Jsoup.connect(url)
์ด ๋ฉ์๋๋ ์ง์ ํ URL์ ์ฐ๊ฒฐ์ ์ค๋นํด. ์์ง ์ค์ ๋ก ์์ฒญ์ ๋ณด๋ด์ง๋ ์์์ด.
2. .get()
์ด์ ์ค์ ๋ก HTTP GET ์์ฒญ์ ๋ณด๋ด๊ณ HTML์ ๊ฐ์ ธ์. ๊ทธ๋ฆฌ๊ณ Document ๊ฐ์ฒด๋ก ๋ฐํํด์ค.
3. doc.title()
Document ๊ฐ์ฒด์์ <title> ํ๊ทธ์ ๋ด์ฉ์ ์ถ์ถํด. ์ ๋ง ์ง๊ด์ ์ด์ง?
Document๋ HTML ๋ฌธ์ ์ ์ฒด๋ฅผ ๋ํ๋ด๋ ๊ฐ์ฒด์ผ. DOM(Document Object Model) ๊ตฌ์กฐ๋ก ๋์ด ์์ด์, ์นํ์ด์ง์ ๋ชจ๋ ์์์ ์ ๊ทผํ ์ ์์ง. ๋ง์น HTML ๋ฌธ์๋ฅผ ์๋ฐ ๊ฐ์ฒด๋ก ๋ง๋ ๊ฒ์ฒ๋ผ ์๊ฐํ๋ฉด ๋ผ!
ย
๐ฏ CSS ์ ๋ ํฐ๋ก ์์ ์ ํํ๊ธฐ
์ด์ ์กฐ๊ธ ๋ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ๋ณผ๊น? ๋ด์ค ์ฌ์ดํธ์์ ๊ธฐ์ฌ ์ ๋ชฉ๋ค์ ๊ฐ์ ธ์ค๋ ์ฝ๋์ผ.
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class NewsCrawler {
public static void main(String[] args) {
try {
String url = "https://news.example.com";
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0") // User-Agent ์ค์
.timeout(5000) // ํ์์์ 5์ด
.get();
// CSS ์
๋ ํฐ๋ก ๊ธฐ์ฌ ์ ๋ชฉ ์ ํ
Elements headlines = doc.select("h2.article-title");
System.out.println("์ด " + headlines.size() + "๊ฐ์ ๊ธฐ์ฌ๋ฅผ ์ฐพ์์ต๋๋ค.\n");
// ๊ฐ ์ ๋ชฉ ์ถ๋ ฅ
for (Element headline : headlines) {
String title = headline.text();
String link = headline.select("a").attr("href");
System.out.println("์ ๋ชฉ: " + title);
System.out.println("๋งํฌ: " + link);
System.out.println("---");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
์ค! ์ฝ๋๊ฐ ์กฐ๊ธ ๊ธธ์ด์ก๋ค? ํ์ง๋ง ์ฌ์ ํ ์ดํดํ๊ธฐ ์ฌ์. ํ๋์ฉ ์ดํด๋ณด์. ๐
userAgent() ๋ฉ์๋
์น ์๋ฒ์๊ฒ "๋๋ ์ด๋ฐ ๋ธ๋ผ์ฐ์ ์ผ!"๋ผ๊ณ ์๋ ค์ฃผ๋ ๊ฑฐ์ผ. ์ผ๋ถ ์น์ฌ์ดํธ๋ User-Agent๊ฐ ์์ผ๋ฉด ์ ๊ทผ์ ์ฐจ๋จํ๊ธฐ๋ ํด. ๊ทธ๋์ ์ค์ ๋ธ๋ผ์ฐ์ ์ฒ๋ผ ์์ฅํ๋ ๊ฑฐ์ง. ๐
timeout() ๋ฉ์๋
์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ์ต๋ ์๊ฐ์ ์ค์ ํด. ๋ฐ๋ฆฌ์ด ๋จ์์ผ. 5000์ด๋ฉด 5์ด๋ฅผ ์๋ฏธํ์ง. ๋๋ฌด ๊ธธ๋ฉด ํ๋ก๊ทธ๋จ์ด ๋ฉ์ถ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์๊ณ , ๋๋ฌด ์งง์ผ๋ฉด ๋๋ฆฐ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ชป ๊ฐ์ ธ์ฌ ์ ์์ด.
select() ๋ฉ์๋
์ด๊ฒ ํต์ฌ์ด์ผ! CSS ์
๋ ํฐ๋ฅผ ์ฌ์ฉํด์ ์ํ๋ ์์๋ฅผ ์ ํํด. "h2.article-title"์ "article-title ํด๋์ค๋ฅผ ๊ฐ์ง h2 ํ๊ทธ"๋ฅผ ์๋ฏธํด. jQuery ์จ๋ดค๋ค๋ฉด ์ต์ํ ๊ฑฐ์ผ! ๐ซ
Elements vs Element
Elements๋ ์ฌ๋ฌ ๊ฐ์ Element๋ฅผ ๋ด๋ ์ปฌ๋ ์
์ด์ผ. ๋ฐ๋ณต๋ฌธ์ผ๋ก ํ๋์ฉ ๊บผ๋ด์ ์ฒ๋ฆฌํ ์ ์์ง.
๐จ ๋ค์ํ ์ ๋ ํฐ ํ์ฉ๋ฒ
CSS ์ ๋ ํฐ๋ ์ ๋ง ๊ฐ๋ ฅํ ๋๊ตฌ์ผ. Jsoup์์ ์ฌ์ฉํ ์ ์๋ ๋ค์ํ ์ ๋ ํฐ ํจํด์ ์์๋ณด์. ์ด๊ฑธ ๋ง์คํฐํ๋ฉด ์ฌ๋งํ ์นํ์ด์ง๋ ๋ค ํฌ๋กค๋งํ ์ ์์ด! ๐
๐ ๊ธฐ๋ณธ ์ ๋ ํฐ
// ํ๊ทธ ์ ํ
Elements divs = doc.select("div");
// ํด๋์ค ์ ํ
Elements contents = doc.select(".content");
// ID ์ ํ
Elements header = doc.select("#header");
// ์์ฑ ์ ํ
Elements links = doc.select("a[href]");
// ์์ฑ ๊ฐ์ผ๋ก ์ ํ
Elements pdfLinks = doc.select("a[href$=.pdf]");
// ์ฌ๋ฌ ์กฐ๊ฑด ์กฐํฉ
Elements newsLinks = doc.select("div.news a[href]");
๐ ๊ณ์ธต ๊ตฌ์กฐ ์ ๋ ํฐ
// ์์ ์ ํ (์ง๊ณ ์์๋ง)
Elements directChildren = doc.select("div > p");
// ํ์ ์ ํ (๋ชจ๋ ํ์ ์์)
Elements allDescendants = doc.select("div p");
// ํ์ ์ ํ (๋ฐ๋ก ๋ค์)
Elements nextSibling = doc.select("h2 + p");
// ํ์ ์ ํ (์ดํ ๋ชจ๋ )
Elements allSiblings = doc.select("h2 ~ p");
๐ฏ ๊ณ ๊ธ ์ ๋ ํฐ
// ์ฒซ ๋ฒ์งธ ์์
Element first = doc.selectFirst("div.article");
// n๋ฒ์งธ ์์
Elements third = doc.select("li:nth-child(3)");
// ํ
์คํธ ํฌํจ ์ฌ๋ถ๋ก ์ ํ
Elements matching = doc.select("p:contains(๊ฒ์์ด)");
// ์ ๊ท์์ผ๋ก ์ ํ
Elements regex = doc.select("a[href~=(?i)\\.(png|jpe?g)]");
// ๋ถ์ ์ ํ
Elements notAds = doc.select("div:not(.advertisement)");
์์ฐ! ์ ๋ง ๋ค์ํ์ง? ๐ฒ ์ด ์ ๋๋ง ์์๋ ์ฌ๋งํ ์นํ์ด์ง๋ ๋ค ํ์ฑํ ์ ์์ด. ์ค์ ๋ก ์ฌ์ฉํ ๋๋ ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ(F12)๋ฅผ ์ด์ด์ HTML ๊ตฌ์กฐ๋ฅผ ํ์ธํ๊ณ , ์ ์ ํ ์ ๋ ํฐ๋ฅผ ์ฐพ๋ ๊ฒ ์ค์ํด.
2. "Copy" โ "Copy selector" ์ ํ
3. ๋ณต์ฌ๋ ์ ๋ ํฐ๋ฅผ Jsoup ์ฝ๋์ ๋ถ์ฌ๋ฃ๊ธฐ
์ด๋ ๊ฒ ํ๋ฉด ๋ณต์กํ ์ ๋ ํฐ๋ ์ฝ๊ฒ ์ป์ ์ ์์ด! ๋ค๋ง, ์๋ ์์ฑ๋ ์ ๋ ํฐ๋ ๋๋ฌด ๊ตฌ์ฒด์ ์ผ ์ ์์ผ๋ ์ ์ ํ ๋จ์ํํ๋ ๊ฒ ์ข์. ๐ฏ
๐ ๏ธ ์ค์ ํฌ๋กค๋ง ํ ํฌ๋
์ด์ ์ค์ ์์ ์ ์ฉํ ํ ํฌ๋๋ค์ ์์๋ณด์. ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋์ด์, ์์ ์ ์ด๊ณ ํจ์จ์ ์ธ ํฌ๋กค๋ฌ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ด์ผ. ๐
โฑ๏ธ ์์ ๋ฐ๋ฅธ ํฌ๋กค๋ง: ๋๋ ์ด ์ถ๊ฐํ๊ธฐ
์๋ฒ์ ๋ถ๋ด์ ์ฃผ์ง ์์ผ๋ ค๋ฉด ์์ฒญ ์ฌ์ด์ ์ ์ ํ ๋๋ ์ด๋ฅผ ๋ฃ์ด์ผ ํด. ๋๋ฌด ๋น ๋ฅด๊ฒ ์์ฒญํ๋ฉด ์๋ฒ๊ฐ ๊ณผ๋ถํ๋๊ฑฐ๋, ์ฌํ๋ฉด IP๊ฐ ์ฐจ๋จ๋ ์๋ ์์ด! ๐ฑ
import java.util.concurrent.TimeUnit;
public class PoliteCrawler {
public static void main(String[] args) {
String[] urls = {
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
};
for (String url : urls) {
try {
System.out.println("ํฌ๋กค๋ง ์ค: " + url);
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ
processData(doc);
// 2์ด ๋๊ธฐ (์์ ๋ฐ๋ฅธ ํฌ๋กค๋ง!)
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
System.err.println("์๋ฌ ๋ฐ์: " + e.getMessage());
}
}
}
private static void processData(Document doc) {
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ก์ง
System.out.println("์ ๋ชฉ: " + doc.title());
}
}
๋ณดํต 1~3์ด ์ ๋์ ๋๋ ์ด๊ฐ ์ ๋นํด. ๋ฌผ๋ก ์ฌ์ดํธ๋ง๋ค ๋ค๋ฅด๋๊น robots.txt ํ์ผ์ ํ์ธํ๋ ๊ฒ ์ข์. ์๋ฅผ ๋ค์ด https://example.com/robots.txt์ ์ ์ํ๋ฉด ํฌ๋กค๋ง ๊ท์น์ ๋ณผ ์ ์์ง. ๐ค
๐ ์ฌ์๋ ๋ก์ง ๊ตฌํํ๊ธฐ
๋คํธ์ํฌ๋ ๋ถ์์ ํ ์ ์์ด. ์ผ์์ ์ธ ์ค๋ฅ๋ก ํฌ๋กค๋ง์ด ์คํจํ๋ฉด ์ฌ์๋ํ๋ ๋ก์ง์ ์ถ๊ฐํ์.
public class RetryableCrawler {
private static final int MAX_RETRIES = 3;
private static final int RETRY_DELAY = 2000; // 2์ด
public static Document fetchWithRetry(String url) {
int attempts = 0;
while (attempts < MAX_RETRIES) {
try {
return Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
} catch (IOException e) {
attempts++;
System.err.println("์๋ " + attempts + " ์คํจ: " + e.getMessage());
if (attempts >= MAX_RETRIES) {
System.err.println("์ต๋ ์ฌ์๋ ํ์ ์ด๊ณผ");
throw new RuntimeException("ํฌ๋กค๋ง ์คํจ: " + url, e);
}
try {
Thread.sleep(RETRY_DELAY);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("์ฌ์๋ ์ค ์ธํฐ๋ฝํธ ๋ฐ์", ie);
}
}
}
return null; // ๋๋ฌํ์ง ์์
}
}
๐ช ์ฟ ํค์ ์ธ์ ์ฒ๋ฆฌํ๊ธฐ
๋ก๊ทธ์ธ์ด ํ์ํ ์ฌ์ดํธ๋ฅผ ํฌ๋กค๋งํ๋ ค๋ฉด? ์ฟ ํค์ ์ธ์ ์ ๊ด๋ฆฌํด์ผ ํด. Jsoup์ ์ด๊ฒ๋ ์ง์ํด! ๐ช
import org.jsoup.Connection;
import java.util.Map;
public class LoginCrawler {
public static void main(String[] args) {
try {
// 1๋จ๊ณ: ๋ก๊ทธ์ธ ํ์ด์ง ์ ์ ๋ฐ ์ฟ ํค ํ๋
Connection.Response loginForm = Jsoup.connect("https://example.com/login")
.method(Connection.Method.GET)
.execute();
Map<string, string=""> cookies = loginForm.cookies();
// 2๋จ๊ณ: ๋ก๊ทธ์ธ ์์ฒญ (POST)
Connection.Response loginResponse = Jsoup.connect("https://example.com/login")
.data("username", "myuser")
.data("password", "mypass")
.cookies(cookies)
.method(Connection.Method.POST)
.execute();
// ๋ก๊ทธ์ธ ํ ์ฟ ํค ์
๋ฐ์ดํธ
cookies.putAll(loginResponse.cookies());
// 3๋จ๊ณ: ๋ก๊ทธ์ธ์ด ํ์ํ ํ์ด์ง ์ ์
Document doc = Jsoup.connect("https://example.com/protected-page")
.cookies(cookies)
.get();
System.out.println("๋ก๊ทธ์ธ ์ฑ๊ณต! ํ์ด์ง ์ ๋ชฉ: " + doc.title());
} catch (Exception e) {
e.printStackTrace();
}
}
}</string,>
์ฟ ํค๋ฅผ ์ ์งํ๋ฉด์ ์ฌ๋ฌ ํ์ด์ง๋ฅผ ํ์ํ ์ ์์ด. ๋ง์น ์ค์ ์ฌ์ฉ์์ฒ๋ผ ํ๋ํ๋ ๊ฑฐ์ง! ๐
๐ ํ์ด์ง๋ค์ด์ ์ฒ๋ฆฌํ๊ธฐ
๊ฒ์ํ์ด๋ ์ํ ๋ชฉ๋ก์ฒ๋ผ ์ฌ๋ฌ ํ์ด์ง์ ๊ฑธ์ณ ์๋ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ ค๋ฉด? ํ์ด์ง๋ค์ด์ ์ ์ฒ๋ฆฌํด์ผ์ง!
public class PaginationCrawler {
public static void main(String[] args) {
String baseUrl = "https://example.com/articles?page=";
int maxPages = 10;
for (int page = 1; page <= maxPages; page++) {
try {
String url = baseUrl + page;
System.out.println("\n=== ํ์ด์ง " + page + " ํฌ๋กค๋ง ===");
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
// ๊ธฐ์ฌ ๋ชฉ๋ก ์ถ์ถ
Elements articles = doc.select("div.article");
if (articles.isEmpty()) {
System.out.println("๋ ์ด์ ๊ธฐ์ฌ๊ฐ ์์ต๋๋ค.");
break; // ๋น ํ์ด์ง๋ฉด ์ข
๋ฃ
}
for (Element article : articles) {
String title = article.select("h2").text();
String author = article.select(".author").text();
String date = article.select(".date").text();
System.out.println("์ ๋ชฉ: " + title);
System.out.println("์์ฑ์: " + author);
System.out.println("๋ ์ง: " + date);
System.out.println("---");
}
// ์์ ๋ฐ๋ฅธ ํฌ๋กค๋ง์ ์ํ ๋๋ ์ด
Thread.sleep(2000);
} catch (Exception e) {
System.err.println("ํ์ด์ง " + page + " ํฌ๋กค๋ง ์คํจ: " + e.getMessage());
}
}
}
}
๐พ ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ
ํฌ๋กค๋งํ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ์ ์ฅํ ๊น? ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์๋๋ฐ, ์ํฉ์ ๋ง๊ฒ ์ ํํ๋ฉด ๋ผ. ๐
๐ CSV ํ์ผ๋ก ์ ์ฅํ๊ธฐ
๊ฐ์ฅ ๊ฐ๋จํ๊ณ ๋ฒ์ฉ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ. ์์ ์์๋ ๋ฐ๋ก ์ด ์ ์์ด์ ํธ๋ฆฌํ์ง!
import java.io.FileWriter;
import java.io.IOException;
public class CsvSaver {
public static void saveToCSV(List</code><article><code> articles, String filename) {
try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) {
// CSV ํค๋ ์์ฑ
writer.append("์ ๋ชฉ,์์ฑ์,๋ ์ง,URL\n");
// ๋ฐ์ดํฐ ์์ฑ
for (Article article : articles) {
writer.append(escapeCsv(article.getTitle())).append(",");
writer.append(escapeCsv(article.getAuthor())).append(",");
writer.append(escapeCsv(article.getDate())).append(",");
writer.append(escapeCsv(article.getUrl())).append("\n");
}
System.out.println("CSV ํ์ผ ์ ์ฅ ์๋ฃ: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
// CSV ํน์๋ฌธ์ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ
private static String escapeCsv(String value) {
if (value == null) return "";
if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
value = "\"" + value.replace("\"", "\"\"") + "\"";
}
return value;
}
}
// Article ํด๋์ค
class Article {
private String title;
private String author;
private String date;
private String url;
// ์์ฑ์, getter, setter ์๋ต
}</code></article></pre>
</div>
<h3>๐๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ธฐ</h3>
<p>
๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ค๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ต๊ณ ์ผ. JDBC๋ฅผ ์ฌ์ฉํ ๊ฐ๋จํ ์์ ๋ฅผ ๋ณผ๊น?
</p>
<div class="tx-code-block">
<pre><code>import java.sql.*;
public class DatabaseSaver {
private static final String DB_URL = "jdbc:mysql://localhost:3306/crawling_db";
private static final String USER = "username";
private static final String PASS = "password";
public static void saveToDatabase(List</code><article><code> articles) {
String sql = "INSERT INTO articles (title, author, date, url, created_at) VALUES (?, ?, ?, ?, NOW())";
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
conn.setAutoCommit(false); // ํธ๋์ญ์
์์
for (Article article : articles) {
pstmt.setString(1, article.getTitle());
pstmt.setString(2, article.getAuthor());
pstmt.setString(3, article.getDate());
pstmt.setString(4, article.getUrl());
pstmt.addBatch();
}
int[] results = pstmt.executeBatch();
conn.commit(); // ํธ๋์ญ์
์ปค๋ฐ
System.out.println(results.length + "๊ฐ์ ๊ธฐ์ฌ๊ฐ ์ ์ฅ๋์์ต๋๋ค.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}</code></article></pre>
</div>
<h3>๐ JSON ํ์ผ๋ก ์ ์ฅํ๊ธฐ</h3>
<p>
API ํํ๋ก ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ๋ ค๋ฉด JSON์ด ์ข์. Gson ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ์์ ์ผ.
</p>
<div class="tx-code-block">
<pre><code>import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.FileWriter;
public class JsonSaver {
public static void saveToJson(List</code><article><code> articles, String filename) {
Gson gson = new GsonBuilder()
.setPrettyPrinting() // ๋ณด๊ธฐ ์ข๊ฒ ํฌ๋งทํ
.create();
try (FileWriter writer = new FileWriter(filename, StandardCharsets.UTF_8)) {
gson.toJson(articles, writer);
System.out.println("JSON ํ์ผ ์ ์ฅ ์๋ฃ: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}</code></article></pre>
</div>
<p>
Gson์ ์ฌ์ฉํ๋ ค๋ฉด Maven ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํด:
</p>
<div class="tx-code-block">
<pre><code><dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
๐ญ ๋์ ์นํ์ด์ง ํฌ๋กค๋ง
์ฌ๊ธฐ์ ์ค์ํ ์ด์ผ๊ธฐ๋ฅผ ํด์ผ ํด. Jsoup์ ์ ์ HTML๋ง ํ์ฑํ ์ ์์ด. ๋ฌด์จ ๋ง์ด๋๋ฉด, JavaScript๋ก ๋์ ์ผ๋ก ์์ฑ๋๋ ์ฝํ ์ธ ๋ ๊ฐ์ ธ์ฌ ์ ์๋ค๋ ๊ฑฐ์ผ. ๐ฅ
์์ฆ ๋ง์ ์น์ฌ์ดํธ๋ค์ด React, Vue, Angular ๊ฐ์ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํด์ ์ฝํ ์ธ ๋ฅผ ๋์ ์ผ๋ก ๋ ๋๋งํ์์? ์ด๋ฐ ์ฌ์ดํธ๋ค์ Jsoup๋ง์ผ๋ก๋ ํฌ๋กค๋ง์ด ์ด๋ ค์.
๐ค ๋์ ํ์ด์ง์ธ์ง ํ์ธํ๋ ๋ฐฉ๋ฒ
1. ๋ธ๋ผ์ฐ์ ์์ ํ์ด์ง ์์ค ๋ณด๊ธฐ (Ctrl+U)
2. ์ํ๋ ๋ฐ์ดํฐ๊ฐ ์์ค์ ์๋์ง ํ์ธ
3. ์๋ค๋ฉด? ๋์ ํ์ด์ง์ผ! ๐ช
๐ง ํด๊ฒฐ ๋ฐฉ๋ฒ
๋ฐฉ๋ฒ 1: Selenium ์ฌ์ฉํ๊ธฐ
Selenium์ ์ค์ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ ์ดํด์ JavaScript๊ฐ ์คํ๋ ํ์ HTML์ ๊ฐ์ ธ์ฌ ์ ์์ด. ํ์ง๋ง Jsoup๋ณด๋ค ๋๋ฆฌ๊ณ ๋ฌด๊ฑฐ์.
// Selenium ์์ (์ฐธ๊ณ ์ฉ)
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
WebDriver driver = new ChromeDriver();
driver.get("https://dynamic-site.com");
// JavaScript ์คํ ๋๊ธฐ
Thread.sleep(3000);
// ๋ ๋๋ง๋ HTML ๊ฐ์ ธ์ค๊ธฐ
String html = driver.getPageSource();
// Jsoup์ผ๋ก ํ์ฑ
Document doc = Jsoup.parse(html);
driver.quit();
๋ฐฉ๋ฒ 2: API ์ง์ ํธ์ถํ๊ธฐ
๋ง์ ๋์ ์ฌ์ดํธ๋ค์ด ๋ด๋ถ์ ์ผ๋ก API๋ฅผ ์ฌ์ฉํด. ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ Network ํญ์ ์ด์ด์ API ์๋ํฌ์ธํธ๋ฅผ ์ฐพ์๋ณด์. API๋ฅผ ์ง์ ํธ์ถํ๋ฉด ํจ์ฌ ํจ์จ์ ์ด์ผ! ๐
// API ์ง์ ํธ์ถ ์์
import java.net.http.*;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/articles"))
.header("User-Agent", "Mozilla/5.0")
.build();
HttpResponse<string> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// JSON ํ์ฑ
String jsonData = response.body();
// Gson์ด๋ Jackson์ผ๋ก ํ์ฑ...</string>
โ๏ธ ํฌ๋กค๋ง ์ค๋ฆฌ์ ๋ฒ์ ๊ณ ๋ ค์ฌํญ
๊ธฐ์ ์ ์ผ๋ก ๊ฐ๋ฅํ๋ค๊ณ ํด์ ๋ค ํด๋ ๋๋ ๊ฑด ์๋์ผ. ํฌ๋กค๋งํ ๋ ๊ผญ ์ง์ผ์ผ ํ ์ค๋ฆฌ์ ๋ฒ์ ์ฌํญ๋ค์ ์์๋ณด์. ๐
๐ robots.txt ์ค์ํ๊ธฐ
robots.txt๋ ์น์ฌ์ดํธ๊ฐ ํฌ๋กค๋ฌ์๊ฒ ๋ณด๋ด๋ ์ผ์ข
์ ๊ฐ์ด๋๋ผ์ธ์ด์ผ. ์๋ฅผ ๋ค์ด:
User-agent: *
Disallow: /admin/
Disallow: /private/
Crawl-delay: 10
User-agent: Googlebot
Allow: /
์ด ํ์ผ์ "admin๊ณผ private ํด๋๋ ํฌ๋กค๋งํ์ง ๋ง์ธ์. ๊ทธ๋ฆฌ๊ณ ์์ฒญ ์ฌ์ด์ 10์ด์ฉ ๊ธฐ๋ค๋ ค์ฃผ์ธ์"๋ผ๊ณ ๋งํ๊ณ ์์ด. ์ด๊ฑธ ๋ฌด์ํ๋ฉด ๋ฒ์ ๋ฌธ์ ๊ฐ ๋ ์ ์์ด! โ ๏ธ
๐ซ ํ์ง ๋ง์์ผ ํ ๊ฒ๋ค
1. ๊ฐ์ธ์ ๋ณด ์์ง ๊ธ์ง
์ด๋ฉ์ผ, ์ ํ๋ฒํธ, ์ฃผ์ ๊ฐ์ ๊ฐ์ธ์ ๋ณด๋ฅผ ๋ฌด๋จ์ผ๋ก ์์งํ๋ฉด ๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ ์๋ฐ์ด์ผ. ์ ๋ ํ๋ฉด ์ ๋ผ! ๐
2. ์ ์๊ถ ์นจํด ์ฃผ์
๋ด์ค ๊ธฐ์ฌ, ์ด๋ฏธ์ง, ๋์์ ๋ฑ์ ์ ์๊ถ์ด ์์ด. ๋จ์ํ ๋งํฌ๋ฅผ ๋ชจ์ผ๋ ๊ฑด ๊ด์ฐฎ์ง๋ง, ์ฝํ
์ธ ์์ฒด๋ฅผ ๋ณต์ฌํด์ ์ฌ๋ฐฐํฌํ๋ฉด ๋ฌธ์ ๊ฐ ๋ผ.
3. ์๋ฒ ๊ณผ๋ถํ ์ ๋ฐ ๊ธ์ง
์ด๋น ์๋ฐฑ ๊ฐ์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ๋ค์ด๋ ์ ์์ด. ์ด๊ฑด DDoS ๊ณต๊ฒฉ์ผ๋ก ๊ฐ์ฃผ๋ ์ ์์ด. ํญ์ ์ ์ ํ ๋๋ ์ด๋ฅผ ๋ฃ์! โฑ๏ธ
4. ์ด์ฉ์ฝ๊ด ํ์ธ
๋ง์ ์น์ฌ์ดํธ๊ฐ ์ด์ฉ์ฝ๊ด์์ ํฌ๋กค๋ง์ ๊ธ์งํ๊ณ ์์ด. ํนํ ์์
๋ฏธ๋์ด ํ๋ซํผ๋ค์ ์๊ฒฉํด. ๊ณต์ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์์ ํด.
โก ์ด์ฉ์ฝ๊ด์ ์ฝ์ด๋ดค๋์?
โก ์ ์ ํ ๋๋ ์ด๋ฅผ ์ค์ ํ๋์? (์ต์ 1์ด)
โก User-Agent๋ฅผ ์ค์ ํ๋์?
โก ๊ฐ์ธ์ ๋ณด๋ฅผ ์์งํ์ง ์๋์?
โก ์์ ์ ๋ชฉ์ ์ด๋ผ๋ฉด ๋ฒ๋ฅ ์๋ฌธ์ ๋ฐ์๋์?
โก ๊ณต์ API๊ฐ ์๋์ง ํ์ธํ๋์?
๋ชจ๋ ์ฒดํฌํ๋ค๋ฉด ์์ ํ๊ฒ ํฌ๋กค๋งํ ์ค๋น๊ฐ ๋ ๊ฑฐ์ผ! โจ
๐๏ธ ์ค์ ํ๋ก์ ํธ: ๋ด์ค ํฌ๋กค๋ฌ ๋ง๋ค๊ธฐ
์ด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ์ฌ๋ฌ ๋ด์ค ์ฌ์ดํธ์์ ๊ธฐ์ฌ๋ฅผ ์์งํ๋ ํฌ๋กค๋ฌ์ผ. ๐ฐ
๐ ํ๋ก์ ํธ ๊ตฌ์กฐ
news-crawler/
โโโ src/
โ โโโ crawler/
โ โ โโโ NewsCrawler.java
โ โ โโโ CrawlerConfig.java
โ โ โโโ RateLimiter.java
โ โโโ model/
โ โ โโโ Article.java
โ โโโ parser/
โ โ โโโ Parser.java
โ โ โโโ SiteParser.java
โ โโโ storage/
โ โ โโโ CsvStorage.java
โ โ โโโ DatabaseStorage.java
โ โโโ Main.java
โโโ pom.xml
๐ Article ๋ชจ๋ธ
package model;
import java.time.LocalDateTime;
public class Article {
private String title;
private String content;
private String author;
private String url;
private String source;
private LocalDateTime publishedDate;
private LocalDateTime crawledDate;
public Article() {
this.crawledDate = LocalDateTime.now();
}
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public LocalDateTime getPublishedDate() { return publishedDate; }
public void setPublishedDate(LocalDateTime publishedDate) {
this.publishedDate = publishedDate;
}
public LocalDateTime getCrawledDate() { return crawledDate; }
@Override
public String toString() {
return String.format("[%s] %s - %s", source, title, author);
}
}
โ๏ธ CrawlerConfig
package crawler;
public class CrawlerConfig {
private int timeout = 5000;
private int retryCount = 3;
private int delayBetweenRequests = 2000;
private String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public int getRetryCount() { return retryCount; }
public void setRetryCount(int retryCount) { this.retryCount = retryCount; }
public int getDelayBetweenRequests() { return delayBetweenRequests; }
public void setDelayBetweenRequests(int delay) {
this.delayBetweenRequests = delay;
}
public String getUserAgent() { return userAgent; }
public void setUserAgent(String userAgent) { this.userAgent = userAgent; }
}
๐ฏ SiteParser ์ธํฐํ์ด์ค
package parser;
import model.Article;
import org.jsoup.nodes.Document;
import java.util.List;
public interface SiteParser {
String getSiteName();
List<string> getArticleUrls(Document doc);
Article parseArticle(Document doc);
}</string>
๐ฐ ๊ตฌ์ฒด์ ์ธ ํ์ ๊ตฌํ
package parser;
import model.Article;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.List;
public class ExampleNewsParser implements SiteParser {
@Override
public String getSiteName() {
return "Example News";
}
@Override
public List<string> getArticleUrls(Document doc) {
List<string> urls = new ArrayList<>();
Elements links = doc.select("div.article-list a.article-link");
for (Element link : links) {
String url = link.absUrl("href");
if (!url.isEmpty()) {
urls.add(url);
}
}
return urls;
}
@Override
public Article parseArticle(Document doc) {
Article article = new Article();
article.setSource(getSiteName());
// ์ ๋ชฉ ์ถ์ถ
Element titleElement = doc.selectFirst("h1.article-title");
if (titleElement != null) {
article.setTitle(titleElement.text());
}
// ์์ฑ์ ์ถ์ถ
Element authorElement = doc.selectFirst("span.author-name");
if (authorElement != null) {
article.setAuthor(authorElement.text());
}
// ๋ณธ๋ฌธ ์ถ์ถ
Element contentElement = doc.selectFirst("div.article-content");
if (contentElement != null) {
article.setContent(contentElement.text());
}
// URL ์ค์
article.setUrl(doc.location());
return article;
}
}</string></string>
๐ ๋ฉ์ธ ํฌ๋กค๋ฌ
package crawler;
import model.Article;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import parser.SiteParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class NewsCrawler {
private CrawlerConfig config;
private SiteParser parser;
public NewsCrawler(CrawlerConfig config, SiteParser parser) {
this.config = config;
this.parser = parser;
}
public List</code><article><code> crawl(String listPageUrl) {
List</code><article><code> articles = new ArrayList<>();
try {
System.out.println("๋ชฉ๋ก ํ์ด์ง ํฌ๋กค๋ง: " + listPageUrl);
Document listDoc = fetchDocument(listPageUrl);
List<string> articleUrls = parser.getArticleUrls(listDoc);
System.out.println(articleUrls.size() + "๊ฐ์ ๊ธฐ์ฌ URL ๋ฐ๊ฒฌ");
for (String url : articleUrls) {
try {
System.out.println("๊ธฐ์ฌ ํฌ๋กค๋ง: " + url);
Document articleDoc = fetchDocument(url);
Article article = parser.parseArticle(articleDoc);
articles.add(article);
// ๋๋ ์ด
TimeUnit.MILLISECONDS.sleep(config.getDelayBetweenRequests());
} catch (Exception e) {
System.err.println("๊ธฐ์ฌ ํฌ๋กค๋ง ์คํจ: " + url);
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("๋ชฉ๋ก ํ์ด์ง ํฌ๋กค๋ง ์คํจ");
e.printStackTrace();
}
return articles;
}
private Document fetchDocument(String url) throws IOException {
int attempts = 0;
while (attempts < config.getRetryCount()) {
try {
return Jsoup.connect(url)
.userAgent(config.getUserAgent())
.timeout(config.getTimeout())
.get();
} catch (IOException e) {
attempts++;
if (attempts >= config.getRetryCount()) {
throw e;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("์ฌ์๋ ์ค ์ธํฐ๋ฝํธ", ie);
}
}
}
throw new IOException("๋ฌธ์ ๊ฐ์ ธ์ค๊ธฐ ์คํจ");
}
}</string></code></article></article></pre>
</div>
<h3>๐ฌ Main ํด๋์ค</h3>
<div class="tx-code-block">
<pre><code>import crawler.CrawlerConfig;
import crawler.NewsCrawler;
import model.Article;
import parser.ExampleNewsParser;
import storage.CsvStorage;
import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("=== ๋ด์ค ํฌ๋กค๋ฌ ์์ ===\n");
// ์ค์
CrawlerConfig config = new CrawlerConfig();
config.setTimeout(5000);
config.setDelayBetweenRequests(2000);
// ํ์ ์ค์
ExampleNewsParser parser = new ExampleNewsParser();
// ํฌ๋กค๋ฌ ์์ฑ
NewsCrawler crawler = new NewsCrawler(config, parser);
// ํฌ๋กค๋ง ์คํ
String targetUrl = "https://example-news.com/latest";
List</code><article><code> articles = crawler.crawl(targetUrl);
// ๊ฒฐ๊ณผ ์ถ๋ ฅ
System.out.println("\n=== ํฌ๋กค๋ง ์๋ฃ ===");
System.out.println("์ด " + articles.size() + "๊ฐ์ ๊ธฐ์ฌ ์์ง");
for (Article article : articles) {
System.out.println(article);
}
// CSV๋ก ์ ์ฅ
CsvStorage storage = new CsvStorage();
storage.save(articles, "news_articles.csv");
System.out.println("\nํ๋ก๊ทธ๋จ ์ข
๋ฃ");
}
}</code></article></pre>
</div>
<p>
์! ๊ฝค ๋ณธ๊ฒฉ์ ์ธ ํ๋ก์ ํธ๊ฐ ๋์ง? ๐ ์ด ๊ตฌ์กฐ๋ ํ์ฅํ๊ธฐ๋ ์ฌ์. ์๋ก์ด ๋ด์ค ์ฌ์ดํธ๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด SiteParser ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ์ ํด๋์ค๋ง ๋ง๋ค๋ฉด ๋ผ!
</p>
<hr class="tx-divider">
<h2>๐ ๋๋ฒ๊น
๊ณผ ๋ฌธ์ ํด๊ฒฐ</h2>
<p>
ํฌ๋กค๋งํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ ๋ฌธ์ ๋ฅผ ๋ง๋๊ฒ ๋ผ. ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณด์. ๐ง
</p>
<h3>โ ์์ฃผ ๋ง๋๋ ์๋ฌ๋ค</h3>
<p>
<strong>1. HTTP 403 Forbidden</strong><br>
์๋ฒ๊ฐ ์ ๊ทผ์ ๊ฑฐ๋ถํ๋ ๊ฑฐ์ผ. User-Agent๋ฅผ ์ค์ ํ์ง ์์๊ฑฐ๋, ๋ด์ผ๋ก ์ธ์๋์ ๊ฐ๋ฅ์ฑ์ด ๋์.
</p>
<div class="tx-code-block">
<pre><code>// ํด๊ฒฐ ๋ฐฉ๋ฒ
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.referrer("https://www.google.com") // Referrer๋ ์ถ๊ฐ
.get();
2. SocketTimeoutException
์๋ฒ ์๋ต์ด ๋๋ฌด ๋๋ ค์ ํ์์์์ด ๋ฐ์ํ ๊ฑฐ์ผ. ํ์์์ ์๊ฐ์ ๋๋ ค๋ณด์.
// ํ์์์ 10์ด๋ก ์ฆ๊ฐ
Document doc = Jsoup.connect(url)
.timeout(10000)
.get();
3. ์ํ๋ ์์๋ฅผ ์ฐพ์ ์ ์์
์
๋ ํฐ๊ฐ ์๋ชป๋๊ฑฐ๋, ํ์ด์ง ๊ตฌ์กฐ๊ฐ ๋ณ๊ฒฝ๋์ ์ ์์ด. ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ก ๋ค์ ํ์ธํด๋ด!
// ๋ฐฉ์ด์ ์ฝ๋ฉ
Element titleElement = doc.selectFirst("h1.title");
if (titleElement != null) {
String title = titleElement.text();
} else {
System.err.println("์ ๋ชฉ์ ์ฐพ์ ์ ์์ต๋๋ค.");
// ๋์ฒด ์
๋ ํฐ ์๋
titleElement = doc.selectFirst("h1");
}
4. ํ๊ธ ๊นจ์ง ํ์
์ธ์ฝ๋ฉ ๋ฌธ์ ์ผ. UTF-8๋ก ๋ช
์์ ์ผ๋ก ์ค์ ํด์ฃผ์.
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.get();
// ๋๋ HTML ๋ฌธ์์ด์ ํ์ฑํ ๋
Document doc = Jsoup.parse(html, "UTF-8");
๐ ๋๋ฒ๊น ํ
1. HTML ๊ตฌ์กฐ ์ถ๋ ฅํ๊ธฐ
๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ค์ HTML์ ํ์ธํด๋ด.
// ์ ์ฒด HTML ์ถ๋ ฅ
System.out.println(doc.html());
// ํน์ ์์๋ง ์ถ๋ ฅ
Element content = doc.selectFirst("div.content");
if (content != null) {
System.out.println(content.html());
}
2. ๋ก๊น
์ถ๊ฐํ๊ธฐ
์ด๋์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋์ง ์ถ์ ํ์.
import java.util.logging.*;
Logger logger = Logger.getLogger(NewsCrawler.class.getName());
logger.info("ํฌ๋กค๋ง ์์: " + url);
logger.warning("์์๋ฅผ ์ฐพ์ ์ ์์: " + selector);
logger.severe("ํฌ๋กค๋ง ์คํจ: " + e.getMessage());
3. HTML ํ์ผ๋ก ์ ์ฅํ๊ธฐ
๋์ค์ ๋ถ์ํ๊ธฐ ์ํด HTML์ ํ์ผ๋ก ์ ์ฅํด๋๋ฉด ํธํด.
import java.nio.file.*;
String html = doc.html();
Files.writeString(
Paths.get("debug_page.html"),
html,
StandardCharsets.UTF_8
);
โก ์ฑ๋ฅ ์ต์ ํ
๋๋์ ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํ ๋๋ ์ฑ๋ฅ์ด ์ค์ํด. ๋ช ๊ฐ์ง ์ต์ ํ ๊ธฐ๋ฒ์ ์์๋ณด์! ๐
๐ ๋ฉํฐ์ค๋ ๋ฉ ํ์ฉํ๊ธฐ
์ฌ๋ฌ ํ์ด์ง๋ฅผ ๋์์ ํฌ๋กค๋งํ๋ฉด ์๊ฐ์ ํฌ๊ฒ ๋จ์ถํ ์ ์์ด. ExecutorService๋ฅผ ์ฌ์ฉํด๋ณด์.
import java.util.concurrent.*;
import java.util.List;
import java.util.ArrayList;
public class ParallelCrawler {
private static final int THREAD_POOL_SIZE = 5;
public List</code><article><code> crawlParallel(List<string> urls) {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
List<future<article>> futures = new ArrayList<>();
// ๊ฐ URL์ ๋ณ๋ ์ค๋ ๋์์ ์ฒ๋ฆฌ
for (String url : urls) {
Future</future<article></string></code><article><code> future = executor.submit(() -> {
try {
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
return parseArticle(doc);
} catch (Exception e) {
System.err.println("ํฌ๋กค๋ง ์คํจ: " + url);
return null;
}
});
futures.add(future);
}
// ๊ฒฐ๊ณผ ์์ง
List</code><article><code> articles = new ArrayList<>();
for (Future</code><article><code> future : futures) {
try {
Article article = future.get(10, TimeUnit.SECONDS);
if (article != null) {
articles.add(article);
}
} catch (Exception e) {
e.printStackTrace();
}
}
executor.shutdown();
return articles;
}
private Article parseArticle(Document doc) {
// ํ์ฑ ๋ก์ง
return new Article();
}
}</code></article></article></article></article></pre>
</div>
<div class="tx-warning">
<strong>โ ๏ธ ์ฃผ์:</strong> ๋ฉํฐ์ค๋ ๋ฉ์ ์ฌ์ฉํ ๋๋ ์๋ฒ์ ๊ณผ๋ถํ๋ฅผ ์ฃผ์ง ์๋๋ก ์ค๋ ๋ ์๋ฅผ ์ ์ ํ ์ ํํด์ผ ํด. ๋ณดํต 5~10๊ฐ ์ ๋๊ฐ ์ ๋นํด. ๊ทธ๋ฆฌ๊ณ ๊ฐ ์ค๋ ๋์์๋ ๋๋ ์ด๋ฅผ ๋ฃ๋ ๊ฑธ ์์ง ๋ง!
</div>
<h3>๐พ ์บ์ฑ ํ์ฉํ๊ธฐ</h3>
<p>
๊ฐ์ ํ์ด์ง๋ฅผ ์ฌ๋ฌ ๋ฒ ์์ฒญํ์ง ์๋๋ก ์บ์ฑ์ ๊ตฌํํ์.
</p>
<div class="tx-code-block">
<pre><code>import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class CachedCrawler {
private Map<string, document=""> cache = new ConcurrentHashMap<>();
public Document fetchWithCache(String url) throws IOException {
// ์บ์์ ์์ผ๋ฉด ๋ฐํ
if (cache.containsKey(url)) {
System.out.println("์บ์์์ ๋ก๋: " + url);
return cache.get(url);
}
// ์์ผ๋ฉด ํฌ๋กค๋ง
System.out.println("์๋ก ํฌ๋กค๋ง: " + url);
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0")
.timeout(5000)
.get();
// ์บ์์ ์ ์ฅ
cache.put(url, doc);
return doc;
}
public void clearCache() {
cache.clear();
}
}</string,>
๐ ์งํ ์ํฉ ๋ชจ๋ํฐ๋ง
๋๋ ํฌ๋กค๋งํ ๋๋ ์งํ ์ํฉ์ ๋ณผ ์ ์์ผ๋ฉด ์ข์.
public class ProgressTracker {
private int total;
private int completed;
private int failed;
public ProgressTracker(int total) {
this.total = total;
this.completed = 0;
this.failed = 0;
}
public synchronized void incrementCompleted() {
completed++;
printProgress();
}
public synchronized void incrementFailed() {
failed++;
printProgress();
}
private void printProgress() {
int percentage = (int) ((completed + failed) * 100.0 / total);
System.out.printf("\r์งํ๋ฅ : %d%% (%d/%d ์๋ฃ, %d ์คํจ)",
percentage, completed, total, failed);
}
}
๐ ๊ณ ๊ธ ํ ํฌ๋
์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ์ ๋ค์ ์์๋ณด์. ํ๋ก ๊ฐ๋ฐ์๋ค์ด ์ฌ์ฉํ๋ ํ ํฌ๋์ด์ผ! ๐
๐ ํ๋ก์ ์ฌ์ฉํ๊ธฐ
IP ์ฐจ๋จ์ ํผํ๊ฑฐ๋ ์ง์ญ ์ ํ์ ์ฐํํ๋ ค๋ฉด ํ๋ก์๋ฅผ ์ฌ์ฉํ ์ ์์ด.
import java.net.Proxy;
import java.net.InetSocketAddress;
public class ProxyCrawler {
public Document fetchWithProxy(String url, String proxyHost, int proxyPort)
throws IOException {
Proxy proxy = new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort)
);
Document doc = Jsoup.connect(url)
.proxy(proxy)
.userAgent("Mozilla/5.0")
.timeout(10000)
.get();
return doc;
}
}
๐ญ User-Agent ๋กํ ์ด์
๋ค์ํ User-Agent๋ฅผ ๋ฒ๊ฐ์ ์ฌ์ฉํ๋ฉด ๋ด ํ์ง๋ฅผ ํผํ ์ ์์ด.
import java.util.Random;
public class UserAgentRotator {
private static final String[] USER_AGENTS = {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15",
"Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X) AppleWebKit/605.1.15"
};
private Random random = new Random();
public String getRandomUserAgent() {
return USER_AGENTS[random.nextInt(USER_AGENTS.length)];
}
public Document fetch(String url) throws IOException {
return Jsoup.connect(url)
.userAgent(getRandomUserAgent())
.timeout(5000)
.get();
}
}
๐ ์ ๊ทํํ์์ผ๋ก ๋ฐ์ดํฐ ์ ์ ํ๊ธฐ
ํฌ๋กค๋งํ ๋ฐ์ดํฐ์๋ ๋ถํ์ํ ๊ณต๋ฐฑ์ด๋ ํน์๋ฌธ์๊ฐ ๋ง์. ์ ๊ทํํ์์ผ๋ก ๊น๋ํ๊ฒ ์ ์ ํ์.
import java.util.regex.*;
public class DataCleaner {
// ์ฌ๋ฌ ๊ณต๋ฐฑ์ ํ๋๋ก
public static String normalizeWhitespace(String text) {
return text.replaceAll("\\s+", " ").trim();
}
// HTML ํ๊ทธ ์ ๊ฑฐ
public static String removeHtmlTags(String text) {
return text.replaceAll("<[^>]*>", "");
}
// ์ด๋ฉ์ผ ์ถ์ถ
public static List<string> extractEmails(String text) {
List<string> emails = new ArrayList<>();
Pattern pattern = Pattern.compile(
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
emails.add(matcher.group());
}
return emails;
}
// ์ ํ๋ฒํธ ์ถ์ถ (ํ๊ตญ)
public static List<string> extractPhoneNumbers(String text) {
List<string> phones = new ArrayList<>();
Pattern pattern = Pattern.compile(
"0\\d{1,2}-\\d{3,4}-\\d{4}"
);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
phones.add(matcher.group());
}
return phones;
}
// ๊ฐ๊ฒฉ ์ถ์ถ
public static List<integer> extractPrices(String text) {
List<integer> prices = new ArrayList<>();
Pattern pattern = Pattern.compile(
"(\\d{1,3}(,\\d{3})*|\\d+)์"
);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String priceStr = matcher.group(1).replace(",", "");
prices.add(Integer.parseInt(priceStr));
}
return prices;
}
}</integer></integer></string></string></string></string>
๐ ์ฆ๋ถ ํฌ๋กค๋ง ๊ตฌํํ๊ธฐ
๋งค๋ฒ ์ ์ฒด๋ฅผ ํฌ๋กค๋งํ์ง ๋ง๊ณ , ์๋ก์ด ๊ฒ๋ง ๊ฐ์ ธ์ค๋ ์ฆ๋ถ ํฌ๋กค๋ง์ ๊ตฌํํด๋ณด์.
import java.time.LocalDateTime;
import java.util.Set;
import java.util.HashSet;
public class IncrementalCrawler {
private Set<string> crawledUrls = new HashSet<>();
private LocalDateTime lastCrawlTime;
public List</string></code><article><code> crawlNew(String listPageUrl) {
List</code><article><code> newArticles = new ArrayList<>();
try {
Document doc = Jsoup.connect(listPageUrl).get();
Elements articleLinks = doc.select("a.article-link");
for (Element link : articleLinks) {
String url = link.absUrl("href");
// ์ด๋ฏธ ํฌ๋กค๋งํ URL์ ๊ฑด๋๋ฐ๊ธฐ
if (crawledUrls.contains(url)) {
System.out.println("์ด๋ฏธ ํฌ๋กค๋งํจ: " + url);
continue;
}
// ์ ๊ธฐ์ฌ ํฌ๋กค๋ง
Document articleDoc = Jsoup.connect(url).get();
Article article = parseArticle(articleDoc);
// ๋ง์ง๋ง ํฌ๋กค๋ง ์ดํ์ ๊ธฐ์ฌ๋ง
if (lastCrawlTime == null ||
article.getPublishedDate().isAfter(lastCrawlTime)) {
newArticles.add(article);
crawledUrls.add(url);
}
}
lastCrawlTime = LocalDateTime.now();
} catch (Exception e) {
e.printStackTrace();
}
return newArticles;
}
private Article parseArticle(Document doc) {
// ํ์ฑ ๋ก์ง
return new Article();
}
public void saveCrawledUrls(String filename) throws IOException {
Files.write(
Paths.get(filename),
crawledUrls,
StandardCharsets.UTF_8
);
}
public void loadCrawledUrls(String filename) throws IOException {
if (Files.exists(Paths.get(filename))) {
crawledUrls = new HashSet<>(
Files.readAllLines(Paths.get(filename), StandardCharsets.UTF_8)
);
}
}
}</code></article></article></pre>
</div>
<hr class="tx-divider">
<h2>๐ ์ค์ ์ฌ์ฉ ์ฌ๋ก</h2>
<p>
์ด๋ก ๋ง ๋ฐฐ์ฐ๋ฉด ์ฌ๋ฏธ์์์? ์ค์ ๋ก ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ๋ช ๊ฐ์ง ์ฌ๋ก๋ฅผ ๋ณด์. ๐ผ
</p>
<h3>๐ฐ ๊ฐ๊ฒฉ ๋น๊ต ์๋น์ค</h3>
<p>
์ฌ๋ฌ ์ผํ๋ชฐ์ ์ํ ๊ฐ๊ฒฉ์ ์์งํด์ ์ต์ ๊ฐ๋ฅผ ์ฐพ์์ฃผ๋ ์๋น์ค์ผ. ์ค์ ๋ก ๋ง์ ๊ฐ๊ฒฉ ๋น๊ต ์ฌ์ดํธ๋ค์ด ์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ด์๋ผ.
</p>
<div class="tx-code-block">
<pre><code>public class PriceComparison {
public List<productprice> comparePrice(String productName) {
List<productprice> prices = new ArrayList<>();
// ์ฌ๋ฌ ์ผํ๋ชฐ ํฌ๋กค๋ง
prices.addAll(crawlSiteA(productName));
prices.addAll(crawlSiteB(productName));
prices.addAll(crawlSiteC(productName));
// ๊ฐ๊ฒฉ์ ์ ๋ ฌ
prices.sort(Comparator.comparing(ProductPrice::getPrice));
return prices;
}
private List<productprice> crawlSiteA(String productName) {
// ์ฌ์ดํธ A ํฌ๋กค๋ง ๋ก์ง
return new ArrayList<>();
}
}
class ProductPrice {
private String siteName;
private String productName;
private int price;
private String url;
// getters, setters...
}</productprice></productprice></productprice>
๐ ๋ถ๋์ฐ ์์ธ ๋ถ์
๋ถ๋์ฐ ์ฌ์ดํธ์์ ๋งค๋ฌผ ์ ๋ณด๋ฅผ ์์งํด์ ์ง์ญ๋ณ ์์ธ๋ฅผ ๋ถ์ํ ์ ์์ด.
public class RealEstateCrawler {
public List<property> crawlProperties(String region) {
List<property> properties = new ArrayList<>();
try {
String url = "https://realestate-site.com/search?region=" + region;
Document doc = Jsoup.connect(url).get();
Elements listings = doc.select("div.property-listing");
for (Element listing : listings) {
Property property = new Property();
property.setRegion(region);
property.setType(listing.select(".property-type").text());
property.setPrice(parsePrice(listing.select(".price").text()));
property.setArea(parseArea(listing.select(".area").text()));
property.setFloor(listing.select(".floor").text());
properties.add(property);
}
} catch (Exception e) {
e.printStackTrace();
}
return properties;
}
private int parsePrice(String priceText) {
// "3์ต 5์ฒ๋ง์" -> 350000000
// ํ์ฑ ๋ก์ง...
return 0;
}
private double parseArea(String areaText) {
// "85ใก" -> 85.0
return 0.0;
}
}</property></property>
๐ฐ ๋ด์ค ๋ชจ๋ํฐ๋ง ๋ด
ํน์ ํค์๋๊ฐ ํฌํจ๋ ๋ด์ค๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ๊ณ ์๋ฆผ์ ๋ณด๋ด๋ ๋ด์ด์ผ.
import java.util.Timer; import java.util.TimerTask; public class NewsMonitorBot { private SetnotifiedArticles = new HashSet<>(); private List keywords; public NewsMonitorBot(List keywords) { this.keywords = keywords; } public void startMonitoring(int intervalMinutes) { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { checkForNewArticles(); } }, 0, intervalMinutes * 60 * 1000); } private void checkForNewArticles() { try { List articles = crawlNews(); for (Article article : articles) { if (notifiedArticles.contains(article.getUrl())) { continue; } // ํค์๋ ํฌํจ ์ฌ๋ถ ํ์ธ for (String keyword : keywords) { if (article.getTitle().contains(keyword) || article.getContent().contains(keyword)) { sendNotification(article, keyword); notifiedArticles.add(article.getUrl()); break; } } } } catch (Exception e) { e.printStackTrace(); } } private ListcrawlNews() { // ๋ด์ค ํฌ๋กค๋ง ๋ก์ง return new ArrayList<>(); } private void sendNotification(Article article, String keyword) { System.out.println("๐ ํค์๋ '" + keyword + "' ๋ฐ๊ฒฌ!"); System.out.println("์ ๋ชฉ: " + article.getTitle()); System.out.println("URL: " + article.getUrl()); // ์ด๋ฉ์ผ, ์ฌ๋, ํ ๋ ๊ทธ๋จ ๋ฑ์ผ๋ก ์๋ฆผ ์ ์ก } }
์ด๋ฐ ํ๋ก์ ํธ๋ค์ ๋ง๋ค์ด์ **์ฌ๋ฅ๋ท** ๊ฐ์ ํ๋ซํผ์์ ์๋น์ค๋ก ์ ๊ณตํ ์๋ ์์ด. ์ค์ ๋ก ๋ฐ์ดํฐ ์์ง ์๋ํ ์๋น์ค๋ ์์๊ฐ ๋ง๊ฑฐ๋ ! ๐ก
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ๐ ์ฐ๋ฆฌ๋ ์น ํฌ๋กค๋ง์ ๊ธฐ์ด๋ถํฐ ๊ณ ๊ธ ํ ํฌ๋๊น์ง ๋ชจ๋ ์ดํด๋ดค์ด. Jsoup์ ์ฌ์ฉํ๋ฉด ์๋ฐ๋ก๋ ์ฝ๊ณ ๊ฐ๋ ฅํ๊ฒ ์น ํฌ๋กค๋ง์ ํ ์ ์๋ค๋ ๊ฑธ ์์์ ๊ฑฐ์ผ.
โข HTML ํ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โข CSS ์ ๋ ํฐ ์ง์
โข ๊ฐ๋จํ API
2. ํฌ๋กค๋ง ๋ฒ ์คํธ ํ๋ํฐ์ค
โข robots.txt ์ค์
โข ์ ์ ํ ๋๋ ์ด ์ค์
โข User-Agent ์ค์
โข ์๋ฌ ์ฒ๋ฆฌ ๋ฐ ์ฌ์๋
3. ๊ณ ๊ธ ํ ํฌ๋
โข ๋ฉํฐ์ค๋ ๋ฉ
โข ์บ์ฑ
โข ํ๋ก์ ์ฌ์ฉ
โข ์ฆ๋ถ ํฌ๋กค๋ง
4. ๋ฒ์ /์ค๋ฆฌ์ ๊ณ ๋ ค์ฌํญ
โข ๊ฐ์ธ์ ๋ณด ๋ณดํธ
โข ์ ์๊ถ ์กด์ค
โข ์๋ฒ ๋ถํ ์ต์ํ
โข ์ด์ฉ์ฝ๊ด ์ค์
๐ ๋ค์ ๋จ๊ณ
์ด์ ์ฌ๋ฌ๋ถ์ ์น ํฌ๋กค๋ง์ ๊ธฐ๋ณธ์ ๋ง์คํฐํ์ด! ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๋ ค๋ฉด:
1. ์ค์ ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
๋ฐฐ์ด ๋ด์ฉ์ ํ์ฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ด. ํฌํธํด๋ฆฌ์ค๋ก๋ ์ข๊ณ , ์ค์ ์๋น์ค๋ก ๋ง๋ค ์๋ ์์ด! ๐จ
2. Selenium ๋ฐฐ์ฐ๊ธฐ
๋์ ์นํ์ด์ง๋ฅผ ํฌ๋กค๋งํ๋ ค๋ฉด Selenium์ ๋ฐฐ์์ผ ํด. Jsoup๊ณผ ํจ๊ป ์ฌ์ฉํ๋ฉด ์๋ฒฝํ์ง! ๐ค
3. ์ค์ผ์ค๋ง ์ถ๊ฐํ๊ธฐ
Quartz๋ Spring Scheduler๋ฅผ ์ฌ์ฉํด์ ์ ๊ธฐ์ ์ผ๋ก ํฌ๋กค๋งํ๋ ์์คํ
์ ๋ง๋ค์ด๋ด. โฐ
4. ๋ฐ์ดํฐ ๋ถ์ ์ฐ๊ณ
์์งํ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํด์ ์ธ์ฌ์ดํธ๋ฅผ ๋์ถํด๋ด. Python์ pandas๋ ์๋ฐ์ Apache Spark๋ฅผ ํ์ฉํ ์ ์์ด. ๐
5. API ๊ฐ๋ฐ
ํฌ๋กค๋งํ ๋ฐ์ดํฐ๋ฅผ REST API๋ก ์ ๊ณตํ๋ ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ด. Spring Boot์ ํจ๊ป ์ฌ์ฉํ๋ฉด ์ข์! ๐
๐ฌ ๋ง์ง๋ง ์กฐ์ธ
์น ํฌ๋กค๋ง์ ์ ๋ง ์ ์ฉํ ๊ธฐ์ ์ด์ง๋ง, ์ฑ ์๊ฐ ์๊ฒ ์ฌ์ฉํด์ผ ํด. ํญ์ ๋ฒ๊ณผ ์ค๋ฆฌ๋ฅผ ์งํค๊ณ , ๋ค๋ฅธ ์ฌ๋์ ์๋ฒ์ ํผํด๋ฅผ ์ฃผ์ง ์๋๋ก ์ฃผ์ํ์. ๊ทธ๋ฆฌ๊ณ ๊ฐ๋ฅํ๋ฉด ๊ณต์ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๊ฐ์ฅ ์ข๋ค๋ ๊ฑธ ์์ง ๋ง! ๐
ํฌ๋กค๋ง์ ๊ณ์ ์ฐ์ตํ๋ฉด์ ์ค๋ ฅ์ ์์์ผ ํด. ์น์ฌ์ดํธ๋ง๋ค ๊ตฌ์กฐ๊ฐ ๋ค๋ฅด๊ณ , ์์์น ๋ชปํ ๋ฌธ์ ๋ค์ด ์๊ธฐ๊ฑฐ๋ . ํ์ง๋ง ๊ทธ ๊ณผ์ ์์ ๋ง์ด ๋ฐฐ์ฐ๊ฒ ๋ ๊ฑฐ์ผ. ํฌ๊ธฐํ์ง ๋ง๊ณ ๊ณ์ ๋์ ํด๋ด! ๐ช
ํน์ ํฌ๋กค๋ง ํ๋ก์ ํธ๋ฅผ ์งํํ๋ค๊ฐ ๋งํ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด, ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ **์ฌ๋ฅ๋ท** ๊ฐ์ ํ๋ซํผ์์ ๋์์ ๊ตฌํ ์๋ ์์ด. ํจ๊ป ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ ๊ฒ ์ค์ํ๋๊น! ๐ค
์, ์ด์ ์ฌ๋ฌ๋ถ์ ์ฒซ ํฌ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด์ผ! ํ์ด์ ๋น์ด! ๐โจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.