因实验室需求,需要找一个实况天气API。
百度云、阿里云、腾讯云上边我都去找了,很多平台要么没有,要么要收费(免费的可调用次数太少了)。而我在高德开放平台上找到了一个,但是不符合要求,被老师pass掉了。
百度搜一下,基本上都是用Python自动化测试Selenium写的,那也太没意思了吧。找不到,那我只好自己写一个爬虫去爬取了。
分析
如果想在中国天气网上爬取实况天气还是很简单的,但是由于思路一直受限制,所以道路很曲折。
原来是考虑着爬取这个图,每天爬一次就够了。
我费劲脑汁,我还想用x、y的比值来计算出每个点所在的位置然后得出每个点对应的数据。但是经过几次测试之后,误差较大,所以这个方案就放弃了。
然后考虑追溯每个点数据的来源,但是这个网站的js文件用了混淆加密,变量全是a、b、c、dxxxx看不出来意思的英文字母。后来又想想。网页上的数据无非来自两个地方:
- 静态的(在源代码中可以直接找到的)
- 异步请求(在开发者工具栏中network中可以查找)
- 其他的都是玄学
那么根据这个网站,不是静态的。但是network中有那么多的请求,不好找?。
我们就在网站源代码中找js
文件,看看哪个js
文件中有请求。看到一个请求像是在请求数据(一般数据都放在一个服务器上),所以我们根据该请求的Domain
,缩小network中的我们寻找的部分。
在Domain是d1.weather.com.cn
中进行寻找,果然数据基本上都出于这个Domain。
成功了一半,但是请求链接后面加的那一串数字是什么?
http://d1.weather.com.cn/sk_2d/101180101.html?_=1546572266854
越看越像时间戳,然后再eclipse
中测试一下,果然是时间戳。
那么问题就简单了。直接写代码就行了。
总结
遇到爬取任务时,要先分析:
找数据
- 看数据是否就在网站源代码中
- 看数据是否是异步加载的
- 若network中请求不多,直接遍历查看请求即可。
- 反之,通过请求链接寻找
Domain
,然后缩小范围寻找请求。
- 如果上两个都不容易,那就在开发者工具中
Break on subtree modifications
调试js文件- 如果js文件使用了混淆加密,你不想掉头发的话,就直接找异步请求吧。
检查数据
要知道很多网站都有反爬虫机制,也就是说 你获取的数据中可能被"投了毒"。好好检查检查数据是否正确。
写代码爬取数据
要注意以下几部分
- 请求头伪装的像浏览器一点
- 让爬虫随机获取
user-agent
- 设置
referer
(这个还是比较有用的)
- 让爬虫随机获取
- 请求之间有一些延迟最好
- 如果需要的话,就上下边的终极武器。
- 构造
cookies
池 - 构造
IP
池
- 构造
Java代码
我用了quartz
定时任务框架来实现定时爬取。
public void execute(JobExecutionContext arg0) {
String[] city = new String[124];
// 读取城市ID
int i = 0;
String str = "";
try {
URL resource = this.getClass().getResource("/cityId.txt");
String path = resource.getPath();// 获取文件的绝对路径
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(path)));
while ((str = br.readLine()) != null) {
city[i] = str;
i++;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setSocketTimeout(60000).setConnectTimeout(60000)
.setConnectionRequestTimeout(60000)
.setStaleConnectionCheckEnabled(true).build();
// 创建httpClient对象
CloseableHttpClient h = HttpClients.custom()
.setDefaultRequestConfig(defaultRequestConfig).build();
// 创建并设置URI
URIBuilder uri = null;
// 创建Get请求
HttpGet hg = null;
String url = "";
// 创建响应对象
CloseableHttpResponse response = null;
InputStream inputstream = null;
for (int j = 0; j < city.length; j++) {
try {
url = "http://d1.weather.com.cn/sk_2d/" + city[j] + ".html?_="
+ System.currentTimeMillis();
uri = new URIBuilder(url);
hg = new HttpGet(uri.build());
} catch (Exception e2) {
e2.printStackTrace();
}
// 设置请求头
hg.setHeader("Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
hg.setHeader("Accept-Encoding", "gzip, deflate");
hg.setHeader("Accept-Language",
"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
hg.setHeader("Cache-Control", "no-cache");
hg.setHeader("Connection", "keep-alive");
hg.setHeader("Host", "d1.weather.com.cn");
hg.setHeader("Upgrade-Insecure-Requests", "1");
hg.setHeader("Cookie",
"f_city=%E9%83%91%E5%B7%9E%7C101180101%7C; Hm_"
+ "lvt_080dabacb001ad3dc8b9b9049b36d"
+ "43b=1546482322; Hm_lpvt_080dabacb001a");
hg.setHeader(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36");
hg.setHeader("Referer",
"http://www.weather.com.cn/weather1dn/101180101.shtml");
// 发送请求
HttpEntity entity = null;
String line = "";
String Sline = "";
try {
response = h.execute(hg);
// 获取请求结果
entity = response.getEntity();
inputstream = entity.getContent();
BufferedReader bufferedreader = new BufferedReader(
new InputStreamReader(inputstream, "UTF-8"));
while ((line = bufferedreader.readLine()) != null) {
Sline += line + "\n";
}
Sline = Sline.substring(Sline.indexOf('{'));
JSONObject jsonObject = JSONObject.fromObject(Sline);
show(jsonObject);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
System.out.println("请求超时等问题");
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
System.out.println("I/O问题");
e1.printStackTrace();
} finally {
try {
inputstream.close();
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("I/O、response流关闭");
e.printStackTrace();
}
}
}
}
/**
* 样例对照表
*
* var dataSK = { "nameen":"zhengzhou", "cityname":"郑州", "city":"101180101",
* "temp":"1", 摄氏度 "tempf":"33", 华氏度 "WD":"西南风", 风向 "wde":"SW", 风向英文
* "WS":"1级", 风力等级 "wse":"<12km/h", 风速 "SD":"52%", 湿度 "time":"11:25",
* "weather":"晴", "weathere":"Sunny", "weathercode":"d00", "qy":"1019", 气压
* "njd":"4.94km", 能见度 "sd":"52%", 湿度 "rain":"0.0", 降雨量 "rain24h":"0",
* "aqi":"214", "limitnumber":"", "aqi_pm25":"214", pm2.5
* "date":"01月03日(星期四)" }
*/
public void show(JSONObject jsonObject) {
// 获取城市编号
String cityId = jsonObject.getString("city");
System.out.println(cityId);
String cityName = jsonObject.getString("cityname");
System.out.println(cityName);
// 获取当前气温
String temperature = jsonObject.getString("temp");
System.out.println("当前气温" + ":" + temperature);
// 获取当前风向
String windDirection = jsonObject.getString("WD");
System.out.println("风向:" + windDirection);
// 获取当前风向
String windDirectionEn = jsonObject.getString("wde");
System.out.println("风向符号:" + windDirectionEn);
// 获取当前风速等级
String windPower = jsonObject.getString("WS");
System.out.println("风力:" + windPower);
// 获取当前风速
String windSpeed = jsonObject.getString("wse");
System.out.println("风力:" + windSpeed);
// 获取当前湿度
String humidity = jsonObject.getString("SD");
System.out.println("湿度:" + humidity);
String time = jsonObject.getString("time");
System.out.println("时间:" + time);
String weather = jsonObject.getString("weather");
System.out.println("天气中文:" + weather);
String weatherEn = jsonObject.getString("weathere");
System.out.println("天气英文:" + weatherEn);
String weatherCode = jsonObject.getString("weathercode");
System.out.println("天气代号:" + weatherCode);
String airPressure = jsonObject.getString("qy");
System.out.println("气压:" + airPressure);
String visibility = jsonObject.getString("njd");
System.out.println("能见度:" + visibility);
String rain = jsonObject.getString("rain");
System.out.println("降雨量:" + rain);
String rain24h = jsonObject.getString("rain24h");
System.out.println("日降雨量" + rain24h);
String aqi_pm25 = jsonObject.getString("aqi");
System.out.println("PM2.5:" + aqi_pm25);
String date = jsonObject.getString("date");
System.out.println("时间" + date);
Connection con = JButil.getConnection();
String sql = "INSERT INTO weather_sk "
+ "(cityId, cityName, lastUpdate, temperature, windSpeed, windPower,"
+ " windDirectionEn, windDirection, humidity, weather, weatherEn, weatherCode,"
+ " visibility, airPressure, rain24h, rain, aqi_pm25, time, date) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
QueryRunner qr = new QueryRunner();
int m = 0;
try {
m = qr.update(con, sql, cityId, cityName,
Timestamp.valueOf(new Date().toLocaleString()),
temperature, windSpeed, windPower, windDirectionEn,
windDirection, humidity, weather, weatherEn, weatherCode,
visibility, airPressure, rain24h, rain, aqi_pm25, time,
date);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DbUtils.closeQuietly(con);
}
}