爬知乎收藏夹

[java]

package com.nbm.spider.zhihu;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontProvider;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.nbm.http.HttpGetter;
import com.nbm.spider.Getter;

public class ZhihuGetter extends Getter
{

private final static Logger log = LoggerFactory.getLogger(ZhihuGetter.class);

private final static String PAGE_TOKEN = "{}";

private final static String PATH_PREFIX = "D:\\kuaipan\\zhihu\\";

private final static int PAGE_PER_FILE = 50;

private int page;

private int currentPage;

private final static int QUESTIONS_PERPAGE = 1000;

private ZhihuFavoriteBean bean;

public ZhihuGetter(String url)
{
bean = new ZhihuFavoriteBean();

bean.setUrl(url);

File file = new File(PATH_PREFIX);

file.mkdirs();
}

public void get()
{
log.info(this.bean.getUrl().replace(PAGE_TOKEN, "1"));

Document doc = null;
try
{
doc = Jsoup.connect(this.bean.getUrl().replace(PAGE_TOKEN, "1")).get();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

bean.setTitle(doc.select("title").text());

Elements pageElement = doc.select("div.border-pager").select("span");
if (pageElement.size() < 2)
{
page = 1;
} else
{
page = Integer.parseInt(pageElement.get(pageElement.size() – 2).text());
}

for (int i = 1; i <= page; i++)
{
String url = this.bean.getUrl().replace(PAGE_TOKEN, "" + i);
getAPage(url);
}

log.debug("共获取到问题{}条", bean.getQuestionBeans().size());

try
{
exportPdf();
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}

}

private void exportPdf() throws DocumentException, IOException
{

// for (int i = 0; i < (bean.getQuestionBeans().size() – 1) / QUESTIONS_PERPAGE; i++)
// {
final BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
BaseFont.NOT_EMBEDDED);
Font boldfont = new Font(bf, 16, Font.BOLD);
com.itextpdf.text.Document document = new com.itextpdf.text.Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(
Paths.get(PATH_PREFIX + bean.getTitle() + ".pdf")
.toFile()));

document.open();

document.add(new Paragraph(bean.getTitle(), boldfont));
document.add(new Paragraph(bean.getUrl(), boldfont));

// int endIndex = (i + 1) * QUESTIONS_PERPAGE <= bean.getQuestionBeans().size()
// ?((i + 1) * QUESTIONS_PERPAGE)
// :bean.getQuestionBeans().size();
for (QuestionBean q : bean.getQuestionBeans())
{
document.add(new Paragraph(q.getQuestion(), boldfont));
document.add(Chunk.NEWLINE);

for (AnswerBean a : q.getAnswerBeans())
{
document.add(new Paragraph(a.getAuthor() + " "
+ a.getAnswerDate(), boldfont));
XMLWorkerHelper.getInstance().parseXHtml(
writer,
document,
new ByteArrayInputStream(Jsoup
.parse(a.getContent())
.html().getBytes()), null,
new FontProvider()
{

@Override
public boolean isRegistered(String arg0)
{
// TODO
// Auto-generated
// method
// stub
return false;
}

@Override
public Font getFont(String arg0,
String arg1,
boolean arg2,
float arg3,
int arg4,
BaseColor arg5)
{
return new Font(bf, 14);
}
});
}
document.add(Chunk.NEWLINE);
document.add(Chunk.NEWLINE);
document.add(Chunk.NEWLINE);

}

document.close();

log.info("[{]}处理完成", bean.getTitle());
// }

}

private void getAPage(String url)
{

try
{

String content = HttpGetter.INSTANCE.getContent(url);

Document doc = Jsoup.parse(content);

Elements contentDivs = doc.select("div.zm-item");

QuestionBean questionBean = new QuestionBean();
for (Iterator<Element> iter = contentDivs.iterator(); iter.hasNext();)
{
Element e = iter.next();

String question = e.select("h2.zm-item-title").text();
if (!StringUtils.isBlank(question))
{
bean.getQuestionBeans().add(questionBean);
questionBean = new QuestionBean();
questionBean.setQuestion(question);
}

AnswerBean answerBean = new AnswerBean();

answerBean.setAuthor(e.select(".zm-item-answer-author-info").text()
.replaceAll("收起", "").trim());
String answer = e.select("textarea.content.hidden").text() + "\n";

// answer =
// StringEscapeUtils.unescapeHtml4(answer).replaceAll("<br>",
// "\n");

Document answerDoc = Jsoup.parse(answer);
answerBean.setAnswerDate(answerDoc.select(
"a.answer-date-link.meta-item").text());
answerDoc.select("span.answer-date-link-wrap").remove();

for (Element element : answerDoc.select("a.member_mention"))
{
element.after(element.text());
element.remove();
}

answerBean.setContent(answerDoc.html());

questionBean.getAnswerBeans().add(answerBean);
}

} catch (IllegalStateException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

public static void main(String[] args) throws IOException
{

String[] numbers =
{ "20547625",
"23512932",
"20021520",
"20148773",
"19650915",
"19720400",
"20189059",
"19981495",
"19591548",
"20393312",
"19610483",
"19567254",
"26460588",
"19630674",
"19960298",
"19662339"};

// Set<String> numbers1 = new HashSet<>();
//
// while(true)
// {
// Document doc = null;
// try
// {
// doc = Jsoup.connect("http://www.zhihu.com/explore").get();
// } catch (IOException e)
// {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
//
// for( Element e : doc.select("ul.list.hot-favlists a"))
// {
// log.debug(e.html());
// numbers1.add(e.attr("href").replace("/collection/", ""));
// }
//
//
// if( numbers1.size() >= 20)
// break;
// }
//
// log.debug(numbers1.toString());
//
for (final String n : numbers)
{
new Thread(new Runnable()
{

@Override
public void run()
{
new ZhihuGetter("http://www.zhihu.com/collection/" + n
+ "?page={}").get();

}
}).start();

}
}

}

[/java]

帮调一个奇怪的jsp显示数据的bug

struts2项目中,同事这样描述了一个bug:

在后台action中执行了一段逻辑A,再调用函数B,该函数为Action中的对象C赋值。在Action中打印C的各字段值正常,jsp页面中,使用el表达式显示C各字段的值,其中字段1有值,字段2无值。

另外一段Action函数中,无逻辑A,后续调用函数B等部分完全一致,返回同一个jsp页面,在前端可以正确显示所有字段的值。

经过大量调试无果后,我介入。过程如下:

  • 将页面上的字段值作各种替换,如输出字段1的部分改为输出字段2,或相反。此步骤是为了排除那种由于视线盲点死活都无法发现的拼写错误。无果。
  • 去掉逻辑A,运行,可以正常显示。恢复逻辑A,又变成异常情况。

至此基本确定错误范围是逻辑A,但逻辑A只是简单的新建变量、赋值等操作,看上去并不像会导致莫名其妙异常的那种代码。

又仔细看了看前端代码,发现其使用方式是用一个struts的iterator标签嵌套了一堆el表达式,el表达式使用的是struts iterator标签中定义的循环变量。再查后发现,该循环变量与逻辑A中的操作的某个变量重名了。所以页面上显示的是逻辑A中操作变量的值。

窗户纸捅破了,其实也没什么太高的技术含量。但调试代码中暴露的问题确实也挺让人头疼。

分而治之的思路如果掌握了,知道先把无关代码都注释了再调能减少很多猜测分支,调bug的思路会大大清晰。

所谓的相同逻辑在两段函数中运行的结果一个正确一个不正确,实际上这两种情况下jsp页面中根本就是访问了两组变量,但debug一下午,测试数据都是完全相同的那一种情况,姓名全叫张三……

不知道给变量规律化起名字的意义,起码循环变量都加个temp前缀,单数复数一个加s一个不加。变量名的起名规则就是,在定义变量的代码处向上数10行,找一个眼熟的名字,这……

一篇充满怨念的记录,项目规模上去之后,事事掌控的模式已经不可行了,分工之后,满眼都是这种无聊的问题。团队建设,看来还任重道远。

===

最近总是精神焦虑,无法集中精力想问题,效率有了很大的降低。生活中的杂事很多,还是要努力克服。黎明不远,加油!

战术服从与战略,执行很重要

对B项目N模块作第二次迭代开发,第一次迭代中的代码基本上废弃不能用了,至少是6人月的损失。

当初需求不定,做这部分开发的战略目标是用模拟的需求确定架构,保证后续的可扩展性和可维护性。但执行过程中失控,开发的中心逐步转向了拉锯模拟需求的各种细节,而代码架构没有得到足够的重视。最终的结果是,得到了一个满足我们想象中需求的可运行版本,但可维护性很差。

在任务执行过程中,每个阶段都回顾是否符合战略目标是必做的事。一旦发生偏离,不要怕推导重来,不要怕承认错误。无论在任何情况下,对人月的浪费都是不可接收的。

Oracle序列环境下Mybatis insert失败时id依然有值

为了在insert之后model类能获取到oracle自动生成的序列值,一般都是这样的配置方法:

[xml]

<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
SELECT XXX_SEQ.NEXTVAL FROM DUAL
</selectKey>

[/xml]

读取序列nextval的执行顺序在insert之前,所以神奇的事情发生了:insert失败,但model类的主键有值,在数据库中居然还查不到这个id!
这个故事告诉我们:一定要做好异常处理。