VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > python爬虫 >
  • 学习日记-从爬虫到接口到APP

最近都在复习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

复制代码
 1 #coding=utf-8
 2 
 3 import sys
 4 import requests
 5 from bs4 import BeautifulSoup
 6 import MySQLdb
 7 
 8 reload(sys)
 9 sys.setdefaultencoding('utf-8')
10 
11 # 定义一个博客类
12 class Blog:
13     title = ""
14     desc = ""
15     postDate = ""
16     status = ""
17 
18 # 进行网络请求拉取源码,地址为我博客首页
19 response = requests.get("http://www.cnblogs.com/Fndroid/")
20 # 使用BeautifulSoup进行处理
21 soup = BeautifulSoup(response.text, "html.parser")
22 
23 blogs = []
24 # 根据源码格式爬取栏目,找到class为day的标签,获取并遍历其子div
25 for day in soup.findAll(class_='day'):
26     divs = day.findAll("div")
27     n = 0
28     b = Blog()
29     for div in divs:
30         if n == 0:
31             # 爬取发表时间
32             b.postDate = div.a.string
33         elif n == 1:
34             # 爬取标题
35             b.title = div.a.string
36         elif n == 2:
37             # 爬取摘要
38             b.desc = div.div.contents[0]
39         elif n == 5:
40             # 爬取文章状态
41             b.status = div.contents[0]
42         elif n == 6:
43             n = 0
44             blogs.append(b)
45             break
46 
47         n += 1
48 
49 # 连接数据库,数据库用户名root,密码root,数据库名myblog,编码格式utf8
50 db = MySQLdb.connect("localhost", "root", "root", "myblog", charset="utf8")
51 cursor = db.cursor()
52 
53 for bl in blogs:
54     sub_sql = "'"+bl.title+"','"+bl.desc+"','"+bl.postDate+"','"+bl.status+"'"
55     # 构造sql语句,插入数据
56     sql = "insert into Blog(title,description,post_date,post_status) values("+sub_sql+")"
57     try:
58         cursor.execute(sql)
59         db.commit()
60     except:
61         db.rollback()
62 
63 db.close()
复制代码

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

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

 

③ 接口开发

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

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

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

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

复制代码
 1 public class Blog implements java.io.Serializable {
 2 
 3     // Fields
 4 
 5     private Integer id;
 6     private String title;
 7     private String description;
 8     private String postDate;
 9     private String postStatus;
10 
11     // Constructors
12 
13     /** default constructor */
14     public Blog() {
15     }
16 
17     /** full constructor */
18     public Blog(String title, String description, String postDate, String postStatus) {
19         this.title = title;
20         this.description = description;
21         this.postDate = postDate;
22         this.postStatus = postStatus;
23     }
24 
25     // Property accessors
26 
27     public Integer getId() {
28         return this.id;
29     }
30 
31     public void setId(Integer id) {
32         this.id = id;
33     }
34 
35     public String getTitle() {
36         return this.title;
37     }
38 
39     public void setTitle(String title) {
40         this.title = title;
41     }
42 
43     public String getDescription() {
44         return this.description;
45     }
46 
47     public void setDescription(String description) {
48         this.description = description;
49     }
50 
51     public String getPostDate() {
52         return this.postDate;
53     }
54 
55     public void setPostDate(String postDate) {
56         this.postDate = postDate;
57     }
58 
59     public String getPostStatus() {
60         return this.postStatus;
61     }
62 
63     public void setPostStatus(String postStatus) {
64         this.postStatus = postStatus;
65     }
66 
67 }
复制代码

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

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 3 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 4 <hibernate-mapping>
 5     <class name="com.fndroid.entity.Blog" table="blog" catalog="myblog">
 6         <id name="id" type="java.lang.Integer">
 7             <column name="id" />
 8             <generator class="identity" />
 9         </id>
10         <property name="title" type="java.lang.String">
11             <column name="title" not-null="true" />
12         </property>
13         <property name="description" type="java.lang.String">
14             <column name="description" not-null="true" />
15         </property>
16         <property name="postDate" type="java.lang.String">
17             <column name="post_date" not-null="true" />
18         </property>
19         <property name="postStatus" type="java.lang.String">
20             <column name="post_status" not-null="true" />
21         </property>
22     </class>
23 </hibernate-mapping>
复制代码

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

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

复制代码
 1 public class BlogDao {
 2 
 3     public List<Blog> getBlogs() {
 4         Configuration conf = new Configuration().configure();
 5         SessionFactory sessionFactory = conf.buildSessionFactory();
 6         Session session = sessionFactory.openSession();
 7         Query query = session.createQuery("from Blog");
 8         List<Blog> list = query.list();
 9         return list;
10     }
11 }
复制代码

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

复制代码
 1 public class BooksAction extends ActionSupport {
 2     
 3     @Override
 4     public String execute() throws Exception {
 5         BlogDao dao = new BlogDao();
 6         List<Blog> blogs = dao.getBlogs();
 7         String result = createJsonString(!blogs.isEmpty(), blogs);
 8         HttpServletRequest request = ServletActionContext.getRequest();
 9         // 将数据填充至内置对象request中,这样在jsp中可以获取得到
10         request.setAttribute("json", createJsonString(!blogs.isEmpty(), blogs));
11         return "success";
12     }
13     
14     /**
15      * 通过数据集生成JSON格式的数据
16      * @param res 数据集是否为空
17      * @param blogs 数据集
18      * @return
19      */
20     private String createJsonString(boolean res, List<Blog> blogs) {
21         JsonObject resultJson = new JsonObject();
22         JsonArray array = new JsonArray();
23         resultJson.addProperty("result", res? 1:0);
24         resultJson.addProperty("err_msg", res? "服务器成功返回数据":"服务器错误");
25         if (res){
26             for (Blog blog : blogs) {
27                 JsonObject bObject = new JsonObject();
28                 bObject.addProperty("id", blog.getId());
29                 bObject.addProperty("title", blog.getTitle());
30                 bObject.addProperty("desc", blog.getDescription());
31                 bObject.addProperty("post_date", blog.getPostDate());
32                 bObject.addProperty("status", blog.getPostStatus());
33                 array.add(bObject);
34             }
35         }
36         resultJson.add("blogs", array);
37         return resultJson.toString();
38     }
39 }
复制代码

配置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的布局(使用数据绑定):

复制代码
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <layout xmlns:android="http://schemas.android.com/apk/res/android">
 3 
 4     <data>
 5 
 6         <variable
 7             name="blog"
 8             type="com.fndroid.retrofitdemo.Blogs.BlogsBean"/>
 9     </data>
10 
11     <LinearLayout
12         android:layout_marginTop="8dp"
13         android:layout_marginBottom="8dp"
14         android:layout_width="match_parent"
15         android:layout_height="wrap_content"
16         android:orientation="vertical">
17 
18         <TextView
19             android:textSize="16sp"
20             android:textAlignment="center"
21             android:text="@{blog.title}"
22             android:layout_width="match_parent"
23             android:layout_height="wrap_content"/>
24 
25         <TextView
26             android:textStyle="italic"
27             android:text="@{blog.desc}"
28             android:layout_width="match_parent"
29             android:layout_height="wrap_content"/>
30 
31         <TextView
32             android:text="@{blog.status}"
33             android:layout_width="match_parent"
34             android:layout_height="wrap_content"/>
35 
36     </LinearLayout>
37 
38 </layout>
复制代码

创建Recycler的Adapter:

复制代码
 1 public class MyAdapter extends RecyclerView.Adapter {
 2     private Context mContext;
 3     private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
 4     public MyAdapter(Context context, ArrayList<Blogs.BlogsBean> blogsArrayList) {
 5         this.mContext = context;
 6         this.mBlogsArrayList = blogsArrayList;
 7     }
 8 
 9     @Override
10     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
11         // 获取绑定实例,并存储在ViewHolder中
12         ItemBlogBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout
13                 .item_blog, parent, false);
14         VH vh = new VH(binding.getRoot());
15         vh.binding = binding;
16         return vh;
17     }
18 
19     @Override
20     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
21         Blogs.BlogsBean blogsBean = mBlogsArrayList.get(position);
22         VH vh = (VH) holder;
23         // 设置数据绑定数据源
24         vh.binding.setVariable(com.fndroid.retrofitdemo.BR.blog, blogsBean);
25     }
26 
27     @Override
28     public int getItemCount() {
29         return mBlogsArrayList.size();
30     }
31 
32     class VH extends RecyclerView.ViewHolder{
33         ItemBlogBinding binding;
34         public VH(View itemView) {
35             super(itemView);
36         }
37     }
38 }
复制代码

编写Retrofit的请求服务:

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

最后编写Activity的内容:

复制代码
 1 public class MainActivity extends AppCompatActivity {
 2     private static final String TAG = "MainActivity";
 3 
 4     // 这里不能写localhost,因为模拟器和服务器ip不同
 5     private static final String URL = "http:192.168.1.181:8080/WebDemo/";
 6     private ArrayList<Blogs.BlogsBean> mBlogsArrayList;
 7     private MyAdapter mMyAdapter;
 8 
 9     @BindView(R.id.main_rv)
10     RecyclerView mRecyclerView;
11 
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.activity_main);
16         ButterKnife.bind(this);
17 
18         mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
19         // 传入空数据源
20         mBlogsArrayList = new ArrayList<>();
21         mMyAdapter = new MyAdapter(this, mBlogsArrayList);
22         mRecyclerView.setAdapter(mMyAdapter);
23 
24         // 使用Gson解析数据,用RxJava2封装请求
25         Retrofit ret = new Retrofit.Builder().baseUrl(URL).addConverterFactory
26                 (GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory
27                 .create()).build();
28         IdentifyService identifyService = ret.create(IdentifyService.class);
29         Observable<Blogs> blogs = identifyService.getBlogs();
30         blogs.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
31                 .subscribe(b -> {
32                     mBlogsArrayList.addAll(b.getBlogs());
33                     mMyAdapter.notifyDataSetChanged();
34                 });
35     }
36 }
复制代码

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

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

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

 

源码地址

 

https://github.com/Fndroid/BlogDemo


相关教程