
我想说明的是我要表达的东西是:编码出独一无二(哪来的独一无二?黑盒技术给我们带来的任意代码实现重复的可能性,这大概是无法完成的)。我想我会继续做下去吧?


实现理想是一个及其艰难的过程,也许你会为自己寻找着借口,让自己不去达到旁边那个理想的自己。但是你只需要付出一点点的努力便会让自己有改变的地方。改变自己总是对自己有着强大的帮助。如果你不知道理想的自己,我想你会在某本书、某句话语、某个人或者自己的内心深处得到正确的答案吧。
最后,我想达到我想达到的样子。
pip install scrapy接下来,讲述如何简单地创建一个爬虫项目,让大家熟悉一下。
scrapy
会得到以下结果:
$ scrapy接着,运行
Scrapy 1.5.2 - no active project
Usage:
scrapy [options] [args]
Available commands:
bench Run quick benchmark test
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy -h" to see more info about a command
scrapy startproject [project_name]
创建一个爬虫项目。
进入 loveletter-spider 目录,再次执行 scrapy
你应该可以看到以下内容
$ scrapy接着,我们可以使用
Scrapy 1.5.2 - project: loveletter
Usage:
scrapy [options] [args]
Available commands:
bench Run quick benchmark test
check Check spider contracts
crawl Run a spider
edit Edit spider
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
list List available spiders
parse Parse URL (using its spider) and print the results
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
Use "scrapy -h" to see more info about a command
scrapy list
显示当前可以运行的 spider。使用 scrapy crawl [spider_name]
可以运行一个爬虫。不管怎样,您可以在上面给出的爬虫链接中得到提示。
我们需要的只是爬虫给我们产生的日志和数据。在上边给出的 loveletter-spider 中是 *.json 和 *.log 在这个爬虫运行结束后会产生这两个文件,我们的服务化过程就有了输入点。然后,我们需要做的就是运行 spider 和处理 json 和 log 文件。在 Java 中运行一条命令使用的是 Runtime#exec() 方法。具体要如做呢?
@Async这样可以记录好爬虫执行状态。处理好爬虫的运行接着我们就需要处理爬取到的数据了,这时候我们可以这样处理例如:
public ListenableFuture<CrawlingResult> crawlFully() {
String runId = getRunId();
String logfile = "crawled/" + runId + ".log";
String outfile = "crawled/" + runId + ".json";
String command = getCommand(outfile, logfile);
//插入爬取结果
CrawlingResult result = getCrawlingResult(runId, outfile, logfile);
crawlingResultRepository.insert(result);
try {
//执行爬虫
Process process = Runtime.getRuntime().exec(command);
result.setStatus(CrawlingResult.RUNNING);
crawlingResultRepository.updateIdempotently(result, CrawlingResult.CREATED);
//等待爬虫执行完成
while (process.isAlive()) {
Thread.sleep(3000);
}
//处理爬虫进程结束退出值
result.setExitValue(process.exitValue());
if (result.getExitValue() == 0) {
result.setStatus(CrawlingResult.FINISHED);
//载入爬取的数据
loadCrawledData(runId);
//分析爬取的数据
analysisCrawledData(runId);
} else {
result.setStatus(CrawlingResult.SPIDER_ERROR);
}
crawlingResultRepository.updateIdempotently(result, CrawlingResult.RUNNING);
return AsyncResult.forValue(result);
} catch (Exception e) {
log.error("spider service terminated:", e);
int previousStatus = result.getStatus();
result.setStatus(CrawlingResult.SYSTEM_ERROR);
crawlingResultRepository.updateIdempotently(result, previousStatus);
return AsyncResult.forExecutionException(e);
}
}
try {这样如果中途出错应该怎么办呢?我们可以使用事务来帮助处理。在 Spring Boot 中我们使用 @Transaction(rollbackFor = IOException.class) 注解处理,若发生问题回滚操作。
//读取爬取的数据
InputStream inputStream = Files.newInputStream(Paths.get(result.getOutfile()));
String rawData = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
List<CrawledData> crawledDataList = JSON.parseArray(rawData, CrawledData.class);
//主题摘要
String[] rawThemeData = null;
Map<String, Theme> map = new HashMap<>();
for (CrawledData crawlData : crawledDataList) {
//处理爬虫数据,结构化地存入数据库
Theme theme = map.get(crawlData.title);
if (Objects.isNull(theme)) {
theme = new Theme();
BeanUtils.copyProperties(crawlData, theme);
rawThemeData = crawlData.title.split("\\|");
theme.setEpisode(rawThemeData[0]);
theme.setId(getThemeId(crawlData.title, crawlData.getPublishTime()));
theme.setRunId(runId);
map.put(crawlData.title, theme);
log.debug("read title data of crawled data: [{}]", theme);
themeRepository.insert(theme);
}
Letter letter = new Letter();
BeanUtils.copyProperties(crawlData, letter);
letter.setTheme(theme);
letter.setRunId(runId);
letter.setId(getLetterId(theme.getId(), crawlData.letterIndex));
log.debug("read letter data of crawled data: [{}]", letter);
letterRepository.insert(letter);
}
} catch (IOException e) {
log.error("can not load the data of crawled data by runId [{}], it throws [{}]", runId, e);
throw e;
}
@Async如果需要控制爬虫本身呢?我们需要把处理这个爬虫的线程
@Transactional(rollbackFor = IOException.class)
public void loadCrawledData(String runId) throws IOException {
CrawlingResult result = crawlingResultRepository.findByRunId(runId);
if (Objects.nonNull(result)) {
// 与上一致
}
}
Thread.currentThread()
保存下来,并对其进行相应的结束,暂停等操作,不过需要注意的是一旦服务重启这个就会失去效用。
具体的爬虫和该示例的源代码:
爬虫:https://github.com/codimiracle/loveletter-spider
服务:https://github.com/codimiracle/loveletter-service