爬取网站的技术很多,我是使用java来写,经过对比还是选择了webmagic来实现。原因就是方便,简单。
文档:webmagic中文文档
源码地址:https://github.com/code4craft/webmagic
文档和代码中的例子都比较简单易懂,几分钟就可以进行网站的爬取和分析了。
这里简单介绍下穷游网的数据抓取(只介绍思路和主要代码,具体代码就不粘贴了,如有需要可留言)
抓取穷游网所有poi的详情页面。
举例:https://place.qyer.com/poi/V2EJalFnBzRTbQ/
如果需要所有的poi页面,我们就要知道这个页面的路径,从上面的导航栏可以看出:目的地> 日本 >东京>东京景点观光>东京迪士尼度假区。
路径是:国家>城市>旅行地>景点>地点
那么我们的抓取的顺序就需要按照这个路径去抓取。
知道路径后,我们就要去分析,页面上的数据是通过get请求能够得到还是需要通过post请求能够得到。如果是get请求,我们就需要知道请求的url。如果是post请求,就需要分析请求的入参有哪些,入参的含义以及如何拿到这些入参。
分析后发现:
我们拿到这些详情页面的url后,然后进行get请求就可以得到html页面了,可以下载下来也可以直接解析出需要的元素。
上面几步涉及到一个问题,就是如何保存这些url和入参呢,我们可以使用数据库进行持久化,通过数据库不仅可以进行数据的保存,还可以区分哪些url已经爬取过了,哪些页面没有爬取过。
如下:
获取景点列表的请求链接举例:https://place.qyer.com/poi.php?action=list_json&page=2&type=city&pid=63&sort=32&subsort=all&isnominate=-1&haslastm=false&rank=6
参数分析:
@Component
public class CountryPageProcessor implements PageProcessor{
private Site site = Site.me()
.setDomain("place.qyer.com")
.setSleepTime(1000)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
@Override
public void process(Page page) {
List list = page.getHtml().xpath("//div[@class='place-home-card4']//a").nodes();
List countryList = Lists.newArrayList();
for(Selectable selectable :list) {
Selectable urlsele = selectable.xpath("//a/@href");
Selectable namesele = selectable.xpath("//a//span/text()");
Selectable nameEnsele = selectable.xpath("//a//em/text()");
if(null != urlsele && null != namesele && null != nameEnsele) {
String url = urlsele.toString();
String name = namesele.toString().trim();
String nameEn = nameEnsele.toString().trim();
String code = url.split("/")[3];
QyCity country = new QyCity();
country.setCode(code);
country.setNameEn(nameEn);;
country.setName(name);
country.setUrl(url);
countryList.add(country);
}
}
page.putField("countryList", countryList);
}
@Override
public Site getSite() {
return site;
}
/*public static void main(String[] args) {
String url = "https://place.qyer.com";
Spider.create(new CountryPageProcessor())
.addUrl(url)
.addPipeline(new ConsolePipeline())
.addPipeline(new CountryPipeline())
.run();
}*/
}
@Component
public class CountryPipeline implements Pipeline{
@Override
public void process(ResultItems resultItems, Task task) {
List list = resultItems.get("countryList");
if(CollectionUtils.isNotEmpty(list)) {
//保存到数据库
}
}
}
@Component
public class CityTravelPageProcessor implements PageProcessor{
private Site site = Site.me()
.setDomain("place.qyer.com")
.setSleepTime(1000)
.setRetryTimes(3)
.setTimeOut(30000)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
@Override
public void process(Page page) {
List list = new JsonPathSelector("$.data.list").selectList(page.getRawText());
List poList = Lists.newArrayList();
for(String json : list) {
Map map = JsonHelper.json2Bean(json, Map.class);
CrawlerTravelDetailPO po = new CrawlerTravelDetailPO();
po.setCnname(null != map.get("cnname") ? map.get("cnname").toString() : "");
po.setRefId(null != map.get("id") ? Long.parseLong(map.get("id").toString()) : 0);
po.setEnname(null != map.get("enname") ? map.get("enname").toString() : "");
po.setGrade(null != map.get("grade") ? map.get("grade").toString() : "");
po.setRank(null != map.get("rank") ? Integer.parseInt(map.get("rank").toString()) : null);
po.setCommentCount(null != map.get("commentCount") ? Integer.parseInt(map.get("commentCount").toString()) : null);
po.setUrl(null != map.get("url") ? map.get("url").toString() : "");
po.setStatus(0);
poList.add(po);
}
page.putField("list", poList);
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
String url = "https://place.qyer.com/poi.php";
Map paraMap = Maps.newHashMap();
paraMap.put("action", "list_json");
paraMap.put("type", "city");
paraMap.put("page", 1);
paraMap.put("pid", 50);
paraMap.put("sort", 32);
Request request = new Request();
request.setUrl(url);
request.setRequestBody(HttpRequestBody.form(paraMap, "UTF-8"));
request.setMethod(HttpConstant.Method.POST);
Spider.create(new CityTravelPageProcessor())
.addRequest(request)
.addPipeline(new ConsolePipeline())
.run();
}
}
@Component
public class CityTravelPipeline implements Pipeline{
@Resource
private CrawlerTravelDetailMapper crawlerTravelDetailMapper;
@Override
public void process(ResultItems resultItems, Task task) {
List list = resultItems.get("list");
if(CollectionUtils.isNotEmpty(list)) {
//保存到数据库
}
}
}
3.下载html
@Component
public class CityGuideDownPageProcessor implements PageProcessor{
private Site site = Site.me()
.setDomain("place.qyer.com")
.setSleepTime(3000)
.setCycleRetryTimes(3)
.setRetryTimes(3)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
@Override
public void process(Page page) {
Html html = page.getHtml();
if(null != html) {
page.putField(QyerConstant.HTML, page.getHtml());
}
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
String url = "https://place.qyer.com/poi/V2wJYFFnBzVTZA/";
Spider.create(new CityGuideDownPageProcessor())
.addUrl(url)
.addPipeline(new ConsolePipeline())
.addPipeline(new FilePipeline("E:\\travel\\"))
.run();
}
}
File in = new File("E:\\travel\\" + fileName);
if(null != in && in.exists()) {
Document doc;
try {
doc = Jsoup.parse(in, "UTF-8", "");
//解析排名,图片和简介
String rankDesc = doc.select("div.poi-placeinfo>div.infos>div>ul>li.rank").text();
String photoSrc = doc.select("div.poi-placeinfo>a>p.coverphoto>img").attr("src");
String summary = doc.select("div.compo-detail-info>div.poi-detail>div>p").text();
} catch (IOException e) {
logger.error("解析报错:id= {}, {}", po.getId(), e);
}
}
以上是穷游爬取的简要逻辑,这里使用数据库进行持久化。主要避免以下几个问题: