The hard way, over a long period of time. (Decades!)
I started out writing parsing code by hand, eventually
worked out that parsing email headers was easy with a
finite state machine, got into parser generators when
I needed to, eventually absorbed a lot of the theory,
always motivated by projects that needed the work done.
If you find the subject hard to learn don't just blame yourself, blame the subject. The theory and practice of parsing in 2019 is a lot less developed than it really should be.
(1) Small changes in grammar should be small changes in code. For instance, if you want to add an "unless" keyword to a language which is the opposite of "if" that really should be a few lines of code that are physically separate from the rest of the grammar.
(2) Grammars should be composable: you should be able to make a language that is like Java, except you can write
something like
sql(select count(*) from customers)
and be able to do it by referencing existing grammars and just a few lines of code.
(3) Parsers should generate idiomatic data structures. If you are working in Java you should get ordinary objects, maybe in Javascript or Python get JSON-like structures. Small changes in the grammar (say operator precedence)
should involve small changes in the parse tree.
(4) When you make a parser you should get an "unparser" for free that will convert the parse tree back to text
(5) Compiler books will talk about how making error messages that make sense is a hard problem and stop there. I've seen more than one system at this point hit the wall (e.g. Drools) because users could not figure out how to decode the error messages.
I think Parsing Expression Grammars are a step in the right
direction, but I've yet to see one that ticks all the boxes
above.
I find the old parsers for C written by the Bell Labs people interesting because of the hacks involved. Namely, a simple mechanism for operator precedence (try doing that for
PEGs!) and a hackish level of dependence between the early
stage parser and the symbol table. Finally the "callback hell" interface of yacc was a great idea at the time but is insane in modern application languages.