zoukankan      html  css  js  c++  java
  • 学习日记-从爬虫到接口到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:不多说了吧这个

    源码地址

  • 相关阅读:
    【笔记】Maven使用入门
    【笔记】c++文件
    【笔记】IntelliJ IDEA配置Hibernate
    【HTML5校企公益课】第四天
    【c++习题】【17/4/16】动态分配内存
    C#
    C#
    C#
    C#
    C#
  • 原文地址:https://www.cnblogs.com/Fndroid/p/6046179.html
Copyright © 2011-2022 走看看