最近都在复习J2E,多学习一些东西肯定是好的,而且现在移动开发工作都不好找了,有工作就推荐一下小弟呗,广州佛山地区,谢谢了。

这篇博客要做的效果很简单,就是把我博客的第一页每个条目显示在APP上,条目包括标题、摘要和状态,如图:

所以这篇博客将会涉及:

  1. 数据库(MySql)简单设计(建表、插入数据)
  2. 简单爬虫(用Python爬取网页内容,写入数据库)
  3. 简单接口开发(Struts和Hibernate)
  4. APP网络请求(Retrofit、Gson、RxJava等)

大体的流程就是:先创建数据库,通过爬虫手段爬取博客首页的条目内容并填充至数据库,接着搭建简单的JavaWeb后台,提供接口访问,通过网络请求返回数据库中的数据。

① 数据库设计

要爬取数据和接口开发,肯定都是需要先创建数据库和数据表的。这里使用的是MySql,操作的工具是Navicat。对于上面的数据,我们需要建立对应的表:

其中id为主键且自增长。创建完毕可以进行插入和删除等测试。

② 爬取网页数据

静态网页的爬取是比较简单的,其实就是根据网页源码进行解析匹配,而Python的正则表达式较为强大,所以这里使用Python来进行操作,另外,基础的爬虫也可以使用一些库来简化操作,这里会用到request和bs4两个,request用于网络请求,而bs4则是用于解析网页源码得到我们想要的数据。最后,通过MySQLdb对数据进行存储。

先分析网页源码,可以使用Chrome来观察结构:

得到结构之后就可以进行编码:

文件名:MySpider.py

 #coding=utf-8

 import sys
import requests
from bs4 import BeautifulSoup
import MySQLdb reload(sys)
sys.setdefaultencoding('utf-8') # 定义一个博客类
class Blog:
title = ""
desc = ""
postDate = ""
status = "" # 进行网络请求拉取源码,地址为我博客首页
response = requests.get("http://www.cnblogs.com/Fndroid/")
# 使用BeautifulSoup进行处理
soup = BeautifulSoup(response.text, "html.parser") blogs = []
# 根据源码格式爬取栏目,找到class为day的标签,获取并遍历其子div
for day in soup.findAll(class_='day'):
divs = day.findAll("div")
n = 0
b = Blog()
for div in divs:
if n == 0:
# 爬取发表时间
b.postDate = div.a.string
elif n == 1:
# 爬取标题
b.title = div.a.string
elif n == 2:
# 爬取摘要
b.desc = div.div.contents[0]
elif n == 5:
# 爬取文章状态
b.status = div.contents[0]
elif n == 6:
n = 0
blogs.append(b)
break n += 1 # 连接数据库,数据库用户名root,密码root,数据库名myblog,编码格式utf8
db = MySQLdb.connect("localhost", "root", "root", "myblog", charset="utf8")
cursor = db.cursor() for bl in blogs:
sub_sql = "'"+bl.title+"','"+bl.desc+"','"+bl.postDate+"','"+bl.status+"'"
# 构造sql语句,插入数据
sql = "insert into Blog(title,description,post_date,post_status) values("+sub_sql+")"
try:
cursor.execute(sql)
db.commit()
except:
db.rollback() db.close()

主要的功能步骤已经在源码注释中标注了。接着运行程序,查看数据库内容如下则表示正确:

其中id值只要是自增长即可,可以不与上图对应。另外,如果APP需要点击条目跳转到博客内容,还需要把url获取下来,这里只是简单的事例就不拉了。

③ 接口开发

这个接口其实也很好理解,就是通过一个URL访问得到对应的数据,数据格式可以是JSON或者Xml,我们通过这些数据进行页面显示等等。而我们这里使用的是J2E中的Struts和Hibernate来搭建这个简单的后台。Struts用来拦截请求、Hibernate用于操作数据库。

实际上,Python也是可以做到的,但是目前国内很多中小企业都是用的J2E,所以......

环境搭建什么的就不说了,网上一搜一大堆,或者直接使用MyEclipse,快捷方便。

首先,编写对应于数据库的实体Bean:

 public class Blog implements java.io.Serializable {

     // Fields

     private Integer id;
private String title;
private String description;
private String postDate;
private String postStatus; // Constructors /** default constructor */
public Blog() {
} /** full constructor */
public Blog(String title, String description, String postDate, String postStatus) {
this.title = title;
this.description = description;
this.postDate = postDate;
this.postStatus = postStatus;
} // Property accessors public Integer getId() {
return this.id;
} public void setId(Integer id) {
this.id = id;
} public String getTitle() {
return this.title;
} public void setTitle(String title) {
this.title = title;
} public String getDescription() {
return this.description;
} public void setDescription(String description) {
this.description = description;
} public String getPostDate() {
return this.postDate;
} public void setPostDate(String postDate) {
this.postDate = postDate;
} public String getPostStatus() {
return this.postStatus;
} public void setPostStatus(String postStatus) {
this.postStatus = postStatus;
} }

接着在对应包中编写一个Blog.hbn.xml文件,用于Hibernate数据映射:

 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.fndroid.entity.Blog" table="blog" catalog="myblog">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="title" type="java.lang.String">
<column name="title" not-null="true" />
</property>
<property name="description" type="java.lang.String">
<column name="description" not-null="true" />
</property>
<property name="postDate" type="java.lang.String">
<column name="post_date" not-null="true" />
</property>
<property name="postStatus" type="java.lang.String">
<column name="post_status" not-null="true" />
</property>
</class>
</hibernate-mapping>

如果你使用MyEclipse,则这些文件可以通过自带的MyEclipse Hibernate工具生成。

接着,创建一个Dao来获取数据库内容:

 public class BlogDao {

     public List<Blog> getBlogs() {
Configuration conf = new Configuration().configure();
SessionFactory sessionFactory = conf.buildSessionFactory();
Session session = sessionFactory.openSession();
Query query = session.createQuery("from Blog");
List<Blog> list = query.list();
return list;
}
}

最后创建并配置一个Action来拦截请求,并填充数据,这里使用Gson来进行数据包装,所以要记得导入Gson的jar包:

 public class BooksAction extends ActionSupport {

     @Override
public String execute() throws Exception {
BlogDao dao = new BlogDao();
List<Blog> blogs = dao.getBlogs();
String result = createJsonString(!blogs.isEmpty(), blogs);
HttpServletRequest request = ServletActionContext.getRequest();
// 将数据填充至内置对象request中,这样在jsp中可以获取得到
request.setAttribute("json", createJsonString(!blogs.isEmpty(), blogs));
return "success";
} /**
* 通过数据集生成JSON格式的数据
* @param res 数据集是否为空
* @param blogs 数据集
* @return
*/
private String createJsonString(boolean res, List<Blog> blogs) {
JsonObject resultJson = new JsonObject();
JsonArray array = new JsonArray();
resultJson.addProperty("result", res? 1:0);
resultJson.addProperty("err_msg", res? "服务器成功返回数据":"服务器错误");
if (res){
for (Blog blog : blogs) {
JsonObject bObject = new JsonObject();
bObject.addProperty("id", blog.getId());
bObject.addProperty("title", blog.getTitle());
bObject.addProperty("desc", blog.getDescription());
bObject.addProperty("post_date", blog.getPostDate());
bObject.addProperty("status", blog.getPostStatus());
array.add(bObject);
}
}
resultJson.add("blogs", array);
return resultJson.toString();
}
}

配置Struts.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
<package name="books" extends="struts-default">
<action name="listBlogs" method="execute" class="com.fndroid.action.BooksAction">
<result name="success">success.jsp</result>
</action>
</package>
</struts>

success.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%=request.getAttribute("json") %>

直接显示request内置对象中对应的json格式值即可。

接口已经编写完毕,接着启动服务器,并且布置项目,这个时候可以用浏览器访问http://localhost:8080/WebDemo/listBlogs来看是否成功,其中WebDemo为项目名称,listBlogs为Action名:

浏览器效果:

这肯定是不够直观的,所以我们可以尝试一下一些用于开发的请求分析工具,例如Postman(Chrome应用商店下载):

这样就可以看到对应的格式。

④ APP编写

万事俱备,只欠东风了。APP的内容也不多,通过一个RecyclerView显示每个条目的标题、摘要和状态即可。

初始化使用空列表构造一个RecyclerView,接着通过RxJava和Retrofit进行网络请求,得到数据传递给数据列表并刷新界面。

注意:以下代码可能会出现令人身体不适的Lumbda表达式

主界面布局代码省略,里面只有一个RecyclerView。

编写RecyclerView每个Item的布局(使用数据绑定):

 <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable
name="blog"
type="com.fndroid.retrofitdemo.Blogs.BlogsBean"/>
</data> <LinearLayout
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"> <TextView
android:textSize="16sp"
android:textAlignment="center"
android:text="@{blog.title}"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> <TextView
android:textStyle="italic"
android:text="@{blog.desc}"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> <TextView
android:text="@{blog.status}"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> </LinearLayout> </layout>

创建Recycler的Adapter:

 public class MyAdapter extends RecyclerView.Adapter {
private Context mContext;
private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
public MyAdapter(Context context, ArrayList<Blogs.BlogsBean> blogsArrayList) {
this.mContext = context;
this.mBlogsArrayList = blogsArrayList;
} @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 获取绑定实例,并存储在ViewHolder中
ItemBlogBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout
.item_blog, parent, false);
VH vh = new VH(binding.getRoot());
vh.binding = binding;
return vh;
} @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Blogs.BlogsBean blogsBean = mBlogsArrayList.get(position);
VH vh = (VH) holder;
// 设置数据绑定数据源
vh.binding.setVariable(com.fndroid.retrofitdemo.BR.blog, blogsBean);
} @Override
public int getItemCount() {
return mBlogsArrayList.size();
} class VH extends RecyclerView.ViewHolder{
ItemBlogBinding binding;
public VH(View itemView) {
super(itemView);
}
}
}

编写Retrofit的请求服务:

public interface IdentifyService{
@GET("listBlogs")
public Observable<Blogs> getBlogs();
}

最后编写Activity的内容:

 public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; // 这里不能写localhost,因为模拟器和服务器ip不同
private static final String URL = "http:192.168.1.181:8080/WebDemo/";
private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
private MyAdapter mMyAdapter; @BindView(R.id.main_rv)
RecyclerView mRecyclerView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this); mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 传入空数据源
mBlogsArrayList = new ArrayList<>();
mMyAdapter = new MyAdapter(this, mBlogsArrayList);
mRecyclerView.setAdapter(mMyAdapter); // 使用Gson解析数据,用RxJava2封装请求
Retrofit ret = new Retrofit.Builder().baseUrl(URL).addConverterFactory
(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory
.create()).build();
IdentifyService identifyService = ret.create(IdentifyService.class);
Observable<Blogs> blogs = identifyService.getBlogs();
blogs.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
.subscribe(b -> {
mBlogsArrayList.addAll(b.getBlogs());
mMyAdapter.notifyDataSetChanged();
});
}
}

因为使用了各种框架,所以内容也很简单,毕竟都2016年了,谁不用框架对吗。

这里对使用的各种框架做一个简单的说明:

  • RxJava2:异步请求必须要掌握的
  • Retrofit:它聪明的提供了Gson、RxJava2等支持,底层也是基于okhttp,所以性能也较好,也是必须掌握的
  • databinding(数据绑定):官方出品,免去setText、findViewById等冗余代码
  • RetroLumbda:在Java7上提供Lumbda语言支持,毕竟官方默认1.7,改为1.8会导致Instant Run失效,所以你懂的
  • Butterknife:不多说了吧这个

源码地址

学习日记-从爬虫到接口到APP的更多相关文章

  1. Java学习日记-7 抽象类和接口

    一.抽象类 abstract修饰:类和类中的方法 抽象方法:abstract type name(parameter-list);(abstract不能修饰static方法和构造函数) 引用:抽象类有 ...

  2. Python学习日记(二十五) 接口类、抽象类、多态

    接口类 继承有两种用途:继承基类的方法,并且做出自己的改变或扩展(代码重用)和声明某个子类兼容于某基类,定义一个接口类interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子 ...

  3. 学习日记:Python爬虫-1

    这几天在b站看小甲鱼的python3教程,照着写了个有道翻译的程序 代码中字典data中的内容,用浏览器审查元素,先随便输一个要翻译的,找到跳出来的post的那个网址,看formdata就行了 返回的 ...

  4. Python爬虫工程师必学——App数据抓取实战 ✌✌

    Python爬虫工程师必学——App数据抓取实战 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 爬虫分为几大方向,WEB网页数据抓取.APP数据抓取.软件系统 ...

  5. android学习日记05--Activity间的跳转Intent实现

    Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...

  6. android学习日记03--常用控件checkbox/radiobutton

    常用控件3.checkbox 复选框,确定是否勾选,点击一下勾选,点击第二下取消,当有一系列备选项时适合用checkbox控件,方便用户提交数据. 贴上例子Activity的java代码 packag ...

  7. android学习日记03--常用控件button/imagebutton

    常用控件 控件是对数据和方法的封装.控件可以有自己的属性和方法.属性是控件数据的简单访问者.方法则是控件的一些简单而可见的功能.所有控件都是继承View类 介绍android原生提供几种常用的控件bu ...

  8. Linux 学习日记 1

    这是我第一次系统地学习Linux,希望通过这个学习日记收获一些东西把-- @_@ Grub - 启动管理器   在启动时让用户选择要启动的系统.(但是windows比较霸道--重装windows后会将 ...

  9. Python爬虫工程师必学APP数据抓取实战✍✍✍

    Python爬虫工程师必学APP数据抓取实战  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大 ...

随机推荐

  1. Mapreduce的文件和hbase共同输入

    Mapreduce的文件和hbase共同输入 package duogemap;   import java.io.IOException;   import org.apache.hadoop.co ...

  2. Angular2入门系列教程2-项目初体验-编写自己的第一个组件

    上一篇 使用Angular-cli搭建Angular2开发环境 Angular2采用组件的编写模式,或者说,Angular2必须使用组件编写,没有组件,你甚至不能将Angular2项目启动起来 紧接着 ...

  3. Java多线程基础学习(二)

    9. 线程安全/共享变量——同步 当多个线程用到同一个变量时,在修改值时存在同时修改的可能性,而此时该变量只能被赋值一次.这就会导致出现“线程安全”问题,这个被多个线程共用的变量称之为“共享变量”. ...

  4. SQLSERVER走起 APP隆重推出

    SQLSERVER走起 APP隆重推出 为方便大家查看本微信公众以前推送的文章,QQ群里面的某位SQLSERVER重度爱好者开发了<SQLSERVER走起>的APP 以供大家一起交流 网页 ...

  5. Bringing Whoops Back to Laravel 5

    You might be missing the "prettier" Whoops error handler from Laravel 4. If so, here's how ...

  6. nodejs进阶(2)—函数模块调用

    函数调用 1. 文件内普通函数调用 创建一个js文件命名为2_callFunction.js,其中定义一个函数fun1,向返回对象输出了一段字符串“你好,我是fun1”. //------------ ...

  7. javascript数组查重方法总结

    文章参考地址:http://blog.csdn.net/chengxuyuan20100425/article/details/8497277 题目 对下列数组去重: var arr = ['aa', ...

  8. spring注解源码分析--how does autowired works?

    1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...

  9. zookeeper源码分析之二客户端启动

    ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API: create(path, data, flags): 创建一个ZNode, path是其 ...

  10. Discuz论坛黑链清理教程

    本人亲测有效,原创文章哦~~~ 论坛黑链非常的麻烦,如果你的论坛有黑链,那么对不起,百度收录了你的黑链,不会自动删除,需要你手动去清理. 什么是黑链 黑链,顾名思义,就是一些赌博网站的外链,这些黑链相 ...