Have a look at some of the HTML-derived exporter backends, for example https://github.com/yyr/org-mode/blob/master/contrib/lisp/ox-rss.el#L114.
This uses org-export-define-derived-backend
to customize how the HTML backend processes org files:
(org-export-define-derived-backend 'rss 'html
:menu-entry
'(?r "Export to RSS"
((?R "As RSS buffer"
(lambda (a s v b) (org-rss-export-as-rss a s v)))
(?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))
(?o "As RSS file and open"
(lambda (a s v b)
(if a (org-rss-export-to-rss t s v)
(org-open-file (org-rss-export-to-rss nil s v)))))))
:options-alist
'((:description "DESCRIPTION" nil nil newline)
(:keywords "KEYWORDS" nil nil space)
(:with-toc nil nil nil) ;; Never include HTML's toc
(:rss-extension "RSS_EXTENSION" nil org-rss-extension)
(:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)
(:rss-categories nil nil org-rss-categories))
:filters-alist '((:filter-final-output . org-rss-final-function))
:translate-alist '((headline . org-rss-headline)
(comment . (lambda (&rest args) ""))
(comment-block . (lambda (&rest args) ""))
(timestamp . (lambda (&rest args) ""))
(plain-text . org-rss-plain-text)
(section . org-rss-section)
(template . org-rss-template)))
in this, example :filters-alist '((:filter-final-output . org-rss-final-function))
is used to specify a function org-rss-final-function
that can exclude some items from being exported.
Then, :translate-alist '((headline . org-rss-headline) ...
defines a bunch of other functions for certain elements, in this case headlines. These functions will be called with the org data structure of the element, and return the corresponding HTML string.
With this, you should be able to define your own HTML-derived org exporter that outputs exactly what you want.