首先讲下这个项目的功能,及所用到的技术,
1:因怕爬去网页时ip被封,所以用到了代理ip接口,这个接口每请求一次会返回十个ip,及对应端口号 将其split后add进list
2:有了代理ip,下面就可以对网页放心的操作了,但是这时问题来了,并不是所有的代理ip都能用,一旦碰到无用ip就重新请求就太浪费资源,而list直接remove掉无用的元素,再continue到下一次for循环中就会报错
通俗的讲就是list 不允许在遍历的同时remove他的元素 网上找的说法是:
API中此异常的解释:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
原因:
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 Java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
个人的理解是操作的元素销毁了,但是指向这个元素的索引还在,所以再便利时找不到元素就会报错。
解决方案也是在网上找的例子,用迭代器对list中的元素进行操作
Iterator<String[]> iterator = daiLiIp.iterator();
while (iterator.hasNext()) {
String[] ss = iterator.next();
String ip = ss[0];
String num = ss[1];
int port = 0;
try {
port = Integer.parseInt(num);
} catch (NumberFormatException e) {
log.debug("GetSoundCodeUtil类:"+e.getMessage());
}
String connectState = getSoundCode(ip, port, url, codingFormat);// 连接状态只有为false时是连接失败
if ("false".equals(connectState)) {
daiLiIp.remove(ss); // 移除list中无用ip
iterator = null;// 重新置空迭代器
System.gc();// 垃圾回收
iterator = daiLiIp.iterator();// 迭代器重新赋值
if (daiLiIp.size() == 0) {// 如果代理ip集合为0就重新调接口
daiLiIp = GetDaiLiIpUtil.getDaiLiIp();
iterator = daiLiIp.iterator();
}
continue;// 进入下次循环
} else {
return connectState;// 停止循环
}
}
return null;
3:对网页的爬取:
(1):只爬取页面上的数据 用HttpClient 简历连接拿到网页源码
//创建HttpClient对象
HttpClient http = new DefaultHttpClient();
//设置相应时间,设置获取源码时间,设置代理服务器
http.getParams()
.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,100000)//连接超时 指的是连接一个url的连接等待时间
.setParameter(CoreConnectionPNames.SO_TIMEOUT, 100000)//等待客户连接的超时时间
.setParameter(ConnRouteParams.DEFAULT_PROXY, new HttpHost("ip",端口号));
//创建请求对象,get还是post看情况
HttpGet hGet = new HttpGet("http://js.avis.cn/sources/cityshopsdata.js?version=20170301162501");
HttpResponse response = http.execute(hGet);
//EntityUtils工具类把网页实体转换成字符串
String str = EntityUtils.toString(response.getEntity(),"utf-8");
//拿到网页源码再对其进行解析
Elements code = GetElementsUtil.getElements(网页源码,"要拿到的标签交id或class(ul.titles.l)");
for (Element entityCode : code) {//可以继续对拿到的标签进行解析}
(2)爬取到json字符串数据:
如果需要带cookie信息才能拿到数据可以在向父网站发送请求时
Header[] cookies= response.getHeaders("set-cookie");
获取cookie,在向子网站发送请求时携带cookie
hget.addHeader("Cookie",cookie);
截取出有效的json字符,再将字符串转为实体类,在定义实体类时要完全按着json属性定义
@JsonProperty(value = "address")
private String address;
再用ObjectMapper将json字符串转为实体类
List<JsonObject> cityStoreList = null;
ObjectMapper oMapper = new ObjectMapper();
try {
// json字符串转对象
cityStoreList = oMapper.readValue(cityJson,
new TypeReference<List<EntityObject>>() {
});
if (cityStoreList != null && cityStoreList.size() > 0) {
//对含实体类对象的list进行操作
}
} else {
log.debug("");
}
} catch (Exception e) {
log.debug(e.getMessage());
}
拿到数据再入库,或者导入excel
这个项目用了配置文件
第一次在项目中运用配置文件,在一些可能会变更的地方用配置操作,个人感觉配置文件方便了代码的维护
错误日志用的log4j网上找的方案
坚持每个项目都写一个